Sprievodca po sun.misc.nebezpečný

1. Prehľad

V tomto článku sa pozrieme na fascinujúcu triedu poskytovanú JRE - Nebezpečný z slnko.misc balíček. Táto trieda nám poskytuje nízkoúrovňové mechanizmy, ktoré boli navrhnuté tak, aby ich mohla používať iba hlavná knižnica Java, a nie štandardní používatelia.

To nám poskytuje nízkoúrovňové mechanizmy primárne určené na interné použitie v základných knižniciach.

2. Získanie inštancie Nebezpečný

Po prvé, aby bolo možné používať Nebezpečný triedy, musíme získať inštanciu - čo nie je priamočiare, pretože trieda bola navrhnutá iba na interné použitie.

Inštanciu je možné získať pomocou statickej metódy getUnsafe (). Výhradou je, že predvolene - toto vyvolá a Výnimka zabezpečenia.

Našťastie inštanciu môžeme získať pomocou odrazu:

Pole f = Unsafe.class.getDeclaredField ("theUnsafe"); f.setAccessible (true); unsafe = (Nebezpečný) f.get (null);

3. Vytvorenie triedy pomocou triedy Nebezpečný

Povedzme, že máme jednoduchú triedu s konštruktorom, ktorý nastavuje premennú hodnotu pri vytváraní objektu:

trieda InitializationOrdering {private long a; public InitializationOrdering () {this.a = 1; } public long getA () {return this.a; }}

Keď tento objekt inicializujeme pomocou konštruktora, značka dostať() metóda vráti hodnotu 1:

InitializationOrdering o1 = nový InitializationOrdering (); assertEquals (o1.getA (), 1);

Môžeme však použiť allocateInstance () metóda pomocou Nebezpečný. Iba vyhradí pamäť pre našu triedu a nevyvolá konštruktor:

InitializationOrdering o3 = (InitializationOrdering) unsafe.allocateInstance (InitializationOrdering.class); assertEquals (o3.getA (), 0);

Všimnite si, že konštruktér nebol vyvolaný a kvôli tejto skutočnosti dostať() metóda vrátila predvolenú hodnotu pre dlho typ - čo je 0.

4. Zmena súkromných polí

Povedzme, že máme triedu, ktorá má a tajomstvo súkromná hodnota:

trieda SecretHolder {private int SECRET_VALUE = 0; public boolean secretIsDisclosed () {return SECRET_VALUE == 1; }}

Pomocou putInt () metóda z Nebezpečné, môžeme zmeniť hodnotu súkromného SECRET_VALUE pole, meniace sa / poškodzujúce stav tejto inštancie:

SecretHolder secretHolder = nový SecretHolder (); Pole f = secretHolder.getClass (). GetDeclaredField ("SECRET_VALUE"); unsafe.putInt (secretHolder, unsafe.objectFieldOffset (f), 1); assertTrue (secretHolder.secretIsDisclosed ());

Akonáhle dostaneme pole volaním odrazu, môžeme zmeniť jeho hodnotu na ktorúkoľvek inú int hodnota pomocou Nebezpečný.

5. Vyhodenie výnimky

Kód, ktorý je vyvolaný prostredníctvom Nebezpečný prekladač neskúma rovnako ako bežný kód Java. Môžeme použiť throwException () metóda na vyhodenie akejkoľvek výnimky bez obmedzenia volajúceho na spracovanie tejto výnimky, aj keď ide o kontrolovanú výnimku:

@Test (očakáva sa = IOException.class) public void givenUnsafeThrowException_whenThrowCheckedException_thenNotNeedToCatchIt () {unsafe.throwException (new IOException ()); }

Po hodení an IOException, ktorý je začiarknutý, nemusíme ho chytať ani ho špecifikovať v deklarácii metódy.

6. Neskladovaná pamäť

Ak niektorej aplikácii na JVM dochádza dostupná pamäť, mohli by sme nakoniec vynútiť príliš časté spustenie procesu GC. V ideálnom prípade by sme chceli špeciálnu oblasť pamäte, ktorá by bola mimo haldy a nebola riadená procesom GC.

The allocateMemory () metóda z Nebezpečný trieda nám dáva možnosť alokovať obrovské objekty z haldy, čo znamená táto pamäť nebude GC a JVM zohľadnená a zohľadnená.

To môže byť veľmi užitočné, ale musíme si uvedomiť, že túto pamäť je potrebné spravovať manuálne a správne ju znovu načítať freeMemory () keď už nie sú potrebné.

Povedzme, že chceme vytvoriť veľké pole haldy pamäte typu bajtov. Môžeme použiť allocateMemory () spôsob, ako to dosiahnuť:

trieda OffHeapArray {súkromná konečná statická int BYTE = 1; súkromná dlhá veľkosť; súkromná dlhá adresa; public OffHeapArray (dlhá veľkosť) vyvolá NoSuchFieldException, IllegalAccessException {this.size = size; address = getUnsafe (). allocateMemory (veľkosť * BYTE); } private Unsafe getUnsafe () hodí IllegalAccessException, NoSuchFieldException {Field f = Unsafe.class.getDeclaredField ("theUnsafe"); f.setAccessible (true); návrat (Nebezpečný) f.get (null); } public void set (long i, byte value) throws NoSuchFieldException, IllegalAccessException {getUnsafe (). putByte (address + i * BYTE, value); } public int get (long idx) hodí NoSuchFieldException, IllegalAccessException {return getUnsafe (). getByte (adresa + idx * BYTE); } public long size () {return size; } public void freeMemory () hodí NoSuchFieldException, IllegalAccessException {getUnsafe (). freeMemory (adresa); }
}

V konštruktore OffHeapArray, inicializujeme pole, ktoré je dané veľkosť. Počiatočnú adresu poľa ukladáme do adresa lúka. The sada () metóda berie index a dané hodnotu ktoré budú uložené v poli. The dostať () metóda získava hodnotu bajtu pomocou jej indexu, ktorý je posunom od začiatočnej adresy poľa.

Ďalej môžeme alokovať toto pole mimo haldy pomocou jeho konštruktora:

long SUPER_SIZE = (long) Integer.MAX_VALUE * 2; Pole OffHeapArray = nové OffHeapArray (SUPER_SIZE);

Do tohto poľa môžeme vložiť N čísel bajtových hodnôt a potom tieto hodnoty načítať, ich sčítaním otestovať, či naše adresovanie funguje správne:

int suma = 0; for (int i = 0; i <100; i ++) {array.set ((long) Integer.MAX_VALUE + i, (byte) 3); sum + = array.get ((long) Integer.MAX_VALUE + i); } assertEquals (array.size (), SUPER_SIZE); assertEquals (suma, 300);

Nakoniec musíme uvoľniť pamäť späť do OS volaním freeMemory ().

7. CompareAndSwap Prevádzka

Veľmi efektívne konštrukcie z java.bežne balíček, ako AtomicInteger, používajú compareAndSwap () metódy z Nebezpečný pod, aby poskytovali najlepší možný výkon. Tento konštrukt je široko používaný v algoritmoch bez blokovania, ktoré môžu využiť inštrukciu procesora CAS na zaistenie veľkého zrýchlenia v porovnaní so štandardným pesimistickým synchronizačným mechanizmom v Jave.

Počítadlo založené na CAS môžeme zostrojiť pomocou compareAndSwapLong () metóda z Nebezpečný:

trieda CASCounter {private Nebezpečné nebezpečné; súkromné ​​volatilné dlhé počítadlo = 0; súkromné ​​dlhé vyrovnanie; private Unsafe getUnsafe () hodí IllegalAccessException, NoSuchFieldException {Field f = Unsafe.class.getDeclaredField ("theUnsafe"); f.setAccessible (true); návrat (Nebezpečný) f.get (null); } public CASCounter () hodí výnimku {unsafe = getUnsafe (); offset = unsafe.objectFieldOffset (CASCounter.class.getDeclaredField ("counter")); } public void increment () {long before = counter; while (! unsafe.compareAndSwapLong (this, offset, before, before + 1)) {before = counter; }} public long getCounter () {vratne pult; }}

V CASCounter konštruktora dostávame adresu poľa počítadla, aby sme ju mohli neskôr použiť v prírastok () metóda. Toto pole musí byť deklarované ako volatilné, aby bolo viditeľné pre všetky vlákna, ktoré túto hodnotu zapisujú a čítajú. Používame objectFieldOffset () metóda na získanie adresy pamäte súboru vyrovnať lúka.

Najdôležitejšou súčasťou tejto triedy je prírastok () metóda. Používame compareAndSwapLong () v zatiaľ čo slučka na zvýšenie predtým načítanej hodnoty, kontrola, či sa táto predchádzajúca hodnota zmenila od načítania.

Ak sa to stalo, potom sa snažíme o túto operáciu, kým nebudeme úspešní. Nie je tu blokovanie, a preto sa tomu hovorí algoritmus bez blokovania.

Náš kód môžeme otestovať zvýšením zdieľaného počítadla z viacerých vlákien:

int NUM_OF_THREADS = 1_000; int NUM_OF_INCREMENTS = 10_000; Služba ExecutorService = Executors.newFixedThreadPool (NUM_OF_THREADS); CASCounter casCounter = nový CASCounter (); IntStream.rangeClosed (0, NUM_OF_THREADS - 1) .forEach (i -> service.submit (() -> IntStream .rangeClosed (0, NUM_OF_INCREMENTS - 1) .forEach (j -> casCounter.increment ())));

Ďalej, aby sme tvrdili, že stav počítadla je správny, môžeme z neho získať hodnotu počítadla:

assertEquals (NUM_OF_INCREMENTS * NUM_OF_THREADS, casCounter.getCounter ());

8. Park / Odparkovať

V dokumente sú dve fascinujúce metódy Nebezpečný API, ktoré používa JVM na prepínanie vlákien kontextu. Keď vlákno čaká na nejakú akciu, môže JVM toto vlákno zablokovať pomocou park () metóda z Nebezpečné trieda.

Je to veľmi podobné Object.wait () metóda, ale volá sa natívny kód OS, čím sa využívajú niektoré špecifiká architektúry, aby sa dosiahol najlepší výkon.

Ak je vlákno blokované a je potrebné ho znova spustiť, JVM použije rozbaliť () metóda. Tieto vyvolané metód často uvidíme na výpisoch vlákien, najmä v aplikáciách, ktoré používajú spoločné oblasti vlákien.

9. Záver

V tomto článku sme sa pozreli na Nebezpečné triedy a jej najužitočnejších konštruktov.

Videli sme, ako pristupovať k súkromným poliam, ako alokovať off-haldy pamäte a ako používať konštrukciu porovnania a výmeny na implementáciu bezblokových algoritmov.

Implementáciu všetkých týchto príkladov a útržkov kódu nájdete na GitHub - jedná sa o projekt Maven, takže by malo byť ľahké ho importovať a spustiť tak, ako je.