Útmutató a sun.misc.Nem biztonságos

1. Áttekintés

Ebben a cikkben megnézzük a JRE által nyújtott lenyűgöző osztályt - Nem biztonságos tól sun.misc csomag. Ez az osztály olyan alacsony szintű mechanizmusokat biztosít számunkra, amelyeket csak a Java alapkönyvtár és nem a standard felhasználók számára terveztek használni.

Ez olyan alacsony szintű mechanizmusokat biztosít számunkra, amelyeket elsősorban az alapkönyvtárak belső használatára terveztek.

2. A Nem biztonságos

Először is, hogy képes legyen használni a Nem biztonságos osztályba, be kell szereznünk egy példányt - ami nem egyszerű, mivel az osztályt csak a belső használatra tervezték.

A példány megszerzésének módja a statikus módszer getUnsafe (). A figyelmeztetés az, hogy alapértelmezés szerint - ez a SecurityException.

Szerencsére, tükrözéssel nyerhetjük meg a példányt:

F mező = Nem biztonságos.class.getDeclaredField ("theUnsafe"); f.setAccessible (true); nem biztonságos = (nem biztonságos) f.get (null);

3. Osztály beindítása a használatával Nem biztonságos

Tegyük fel, hogy van egy egyszerű osztályunk egy konstruktorral, amely változó értéket állít be az objektum létrehozásakor:

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

Amikor az objektumot a konstruktor segítségével inicializáljuk, a kap egy() A metódus értéke 1:

InitializationOrdering o1 = új InitializationOrdering (); assertEquals (o1.getA (), 1);

De használhatjuk a alloteInstance () módszer segítségével Nem biztonságos. Csak a memóriát fogja lefoglalni az osztályunk számára, és nem hív meg konstruktort:

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

Figyelje meg, hogy a konstruktort nem hívták meg, és ennek a ténynek a következtében a kap egy() metódus az alapértelmezett értéket adta vissza hosszú típus - ami 0.

4. A privát mezők módosítása

Tegyük fel, hogy van egy osztályunk, amely a titok magánérték:

class SecretHolder {private int SECRET_VALUE = 0; nyilvános logikai titokIsDisclosed () {return SECRET_VALUE == 1; }}

Használni a putInt () módszer től Nem biztonságos, megváltoztathatjuk a privát értékét SECRET_VALUE mező, az adott példány állapotának megváltoztatása / sérülése:

SecretHolder secretHolder = új SecretHolder (); F mező = secretHolder.getClass (). GetDeclaredField ("SECRET_VALUE"); unsafe.putInt (secretHolder, unsafe.objectFieldOffset (f), 1); assertTrue (secretHolder.secretIsDisclosed ());

Amint megkapjuk a mezőt a reflexiós hívással, bármely másra módosíthatjuk az értékét int érték a Nem biztonságos.

5. Kivétel dobása

A kód, amelyen keresztül meghívásra kerül Nem biztonságos a fordító nem vizsgálja ugyanúgy, mint a szokásos Java kódot. Használhatjuk a dobásKivétel () módszer bármilyen kivétel eldobására anélkül, hogy korlátozná a hívót a kivétel kezelésére, még akkor is, ha ez egy bejelölt kivétel:

@Test (várható = IOException.class) public void givenUnsafeThrowException_whenThrowCheckedException_thenNotNeedToCatchIt () {unsafe.throwException (új IOException ()); }

Miután dobott egy IOException, ami be van jelölve, akkor nem kell elkapnunk és megadnunk a method deklarációban sem.

6. Halom nélküli memória

Ha egy alkalmazásból kifogy a JVM szabad memóriája, akkor a GC folyamatot túl gyakran futtathatjuk. Ideális esetben egy speciális memóriaterületet szeretnénk, nem halmozott és nem a GC folyamat által irányított.

A allokMemória () módszer a Nem biztonságos osztály lehetőséget ad arra, hogy hatalmas tárgyakat osszunk el a kupacról, vagyis ez ezt a memóriát a GC és a JVM nem fogja látni és figyelembe venni.

Ez nagyon hasznos lehet, de emlékeznünk kell arra, hogy ezt a memóriát kézzel kell kezelni, és megfelelően vissza kell igényelni szabad memória() amikor már nincs rá szükség.

Tegyük fel, hogy létre akarjuk hozni a bájtok nagy halmozott memóriatömbjét. Használhatjuk a allotMemory () módszer ennek elérésére:

osztály OffHeapArray {privát végleges statikus int BYTE = 1; magán hosszú méret; hosszú magáncím; public OffHeapArray (hosszú méret) dobja a NoSuchFieldException, IllegalAccessException {this.size = size; cím = getUnsafe (). allokMemória (méret * BYTE); } private Unsafe getUnsafe () az IllegalAccessException, NoSuchFieldException {mező f = Unsafe.class.getDeclaredField ("theUnsafe") dob; f.setAccessible (true); return (Nem biztonságos) f.get (null); } public void set (hosszú i, bájt érték) dobja a NoSuchFieldException, IllegalAccessException {getUnsafe (). putByte (cím + i * BYTE, érték); } public int get (long idx) dobja a NoSuchFieldException, IllegalAccessException {return getUnsafe (). getByte (cím + idx * BYTE); } public long size () {return size; } public void freeMemory () dobja a NoSuchFieldException, IllegalAccessException {getUnsafe (). freeMemory (cím); }
}

A. Konstruktorában OffHeapArray, inicializáljuk az adott tömböt méret. A tömb kezdő címét a cím terület. A készlet() módszer az index és az adott felvétele érték amelyet a tömb tárol. A kap() A metódus a bájtérték beolvasása az indexe segítségével, amely eltolódik a tömb kezdőcímétől.

Ezután kioszthatjuk azt az off-heap tömböt a konstruktora segítségével:

hosszú SUPER_SIZE = (hosszú) egész szám.MAX_VALUE * 2; OffHeapArray tömb = új OffHeapArray (SUPER_SIZE);

N bájt értéket helyezhetünk ebbe a tömbbe, majd visszakereshetjük ezeket az értékeket, összegezve azokat, hogy ellenőrizzük, a címzésünk megfelelően működik-e:

int összeg = 0; mert (int i = 0; i <100; i ++) {tömb.készlet ((hosszú) Egész szám.MAX_ÉRTÉK + i, (bájt) 3); összeg + = tömb.get ((hosszú) Egész szám.MAX_ÉRTÉK + i); } assertEquals (tömb.méret (), SUPER_SIZE); assertEquals (összeg, 300);

Végül a memóriát vissza kell szabadítanunk az operációs rendszerhez hívással szabad memória().

7. CompareAndSwap Művelet

A nagyon hatékony konstrukciók a java.egyidejű csomag, tetszik AtomicInteger, a CompareAndSwap () módszerek ki Nem biztonságos alatta, hogy a lehető legjobb teljesítményt nyújtsa. Ezt a konstrukciót széles körben használják a zár nélküli algoritmusokban, amelyek a CAS processzor utasításait kihasználva nagy sebességet képesek biztosítani a Java pesszimista szinkronizálási mechanizmusához képest.

Felépíthetjük a CAS alapú számlálót a CompareAndSwapLong () módszer től Nem biztonságos:

osztály CASCounter {private Unsafe unsafe; privát illékony hosszú számláló = 0; magán hosszú ellentételezés; private Unsafe getUnsafe () dobja az IllegalAccessException, NoSuchFieldException {mező f = Unsafe.class.getDeclaredField ("theUnsafe"); f.setAccessible (true); return (Nem biztonságos) f.get (null); } public CASCounter () dobja a {unsafe = getUnsafe () kivételt; offset = nem biztonságos.objectFieldOffset (CASCounter.class.getDeclaredField ("számláló")); } public void increment () {jóval azelőtt = számláló; while (! unsafe.compareAndSwapLong (this, offset, before, before + 1)) {before = számláló; }} public long getCounter () {return counter; }}

Ban,-ben CASCounter konstruktor megkapjuk a számláló mező címét, hogy később a növekedés() módszer. Ezt a mezőt illékonynak kell nyilvánítani, hogy látható legyen minden szál számára, amely ezt az értéket írja és olvassa. A objectFieldOffset () módszer a. memória címének lekérésére ellentételezés terület.

Ennek az osztálynak a legfontosabb része a növekedés() módszer. Használjuk a CompareAndSwapLong () ban,-ben míg ciklus a korábban beolvasott érték növeléséhez, ellenőrizve, hogy az előző érték megváltozott-e azóta, hogy lekértük.

Ha sikerült, akkor addig próbáljuk újra a műveletet, amíg sikerrel nem járunk. Itt nincs blokkolás, ezért hívják ezt lock-free algoritmusnak.

Tesztelhetjük kódunkat úgy, hogy növeljük a megosztott számlálót több szálból:

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

Ezután annak megállapításához, hogy a számláló állapota megfelelő, megszerezhetjük belőle a számláló értékét:

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

8. Parkolás / parkolás leállítása

Két érdekes módszer létezik a Nem biztonságos API, amelyet a JVM használ a szálak kontextusváltására. Amikor a szál valamilyen műveletre vár, a JVM blokkolhatja ezt a szálat a park() módszer a Nem biztonságos osztály.

Nagyon hasonlít a Object.wait () metódust, de meghívja a natív operációs rendszer kódját, így kihasználva egyes architektúra sajátosságait a legjobb teljesítmény elérése érdekében.

Amikor a szál blokkolva van, és újra futtathatóvá kell tenni, a JVM a unpark () módszer. Gyakran tapasztalhatjuk ezeket a metódusokat a szálkihelyezésekben, különösen a szálkészleteket használó alkalmazásokban.

9. Következtetés

Ebben a cikkben a Nem biztonságos osztály és leghasznosabb konstrukciói.

Láttuk, hogyan lehet hozzáférni a privát mezőkhöz, hogyan lehet kiosztani a halom nélküli memóriát, és hogyan kell használni az összehasonlítás és cserélés konstrukciót a zár nélküli algoritmusok megvalósításához.

Ezeknek a példáknak és kódrészleteknek a megvalósítása megtalálható a GitHub-on - ez egy Maven-projekt, ezért könnyen importálhatónak és futtathatónak kell lennie.