tifyty

pure Java, what else ?

final, fin ül (update 2013-02-11 14:02)

Mire jó a final? Sok mindenre. Például segít programozni, és segít felfedezni olyan hibákat, amikor egy változó nem csak egyszer kaphat értéket, pedig mi azt szerettük volna. Ha például interfészünk van, akkor abban minden változó alapból static és final, ki sem kell írni. Check style alapbeállítás szerint (de lehet, hogy csak helyi default) a check style rád is kiabál, hogy “Ugyanmár ki ne írd! Tudja azt mindenki!” (Ja! Hiszed te! A check style nem szokott felvételiztetni!) Aztán meg a javac fordító olyan okos is tud lenni, mint ebben a bejegyzésben mutattam, hogy egy final változóról tudja már fordítási időben, hogy mi az értéke, és ezért oda se nyúl a kód, például egy int 127-es értékért, hanem egyenesen belefordítja a kódba, mint konstanst.

Szóval erre jó a final. De jó másra is. Mielőtt azonban ebbe belemennénk tisztáznunk kell valamit:

Attól, hogy egy változót final kulcsóval definiáltunk, az objektum, amit ezen keresztül el lehet érni még megváltozhat!

A final tehát nem garantál immutabilitást. Mi is az immutable, azaz megváltoztathatatlan objektum? Amit nem lehet megváltoztatni. Miért szeretjük az ilyen objektumokat? Azért, mert több szálon futó programok esetében kevésbé kell, hogy fájjon a fejünk a szinkronizálással. Ilyen a String, Integer és még sok egyéb objektum. Persze mi magunk is készíthetünk ilyen objektumokat. Ennek egyik módja például az, hogy az objektumon belül minden egyes mező legyen final. Ami presze nem garantálja, hogy azok az objektumok, amelyekre ezek a mezők tartalmaznak referenciát (emlékeztek gyerekek: milyen gyengék tudunk lenni, ha referenciákról van szó), szóval, hogy azok ne változnának. Aztán persze akkor sem járható ez az út, ha az objektum nem igazi immutable, hanem elkészítjük, csinálunk vele ezt, azt és csak ezután már többet nem változik.

Van olyan szokás is a mi utcánkban, vagy máshol (nálunk csak ha van értelme), hogy egyes osztályok két interfészt implementálnak: egy mutátor, és egy immutátor interfészt. A mutátor deklarálja az összes olyan metódust, amelyik meg tudja változtatni az objektum tartalmát, általában ki is terjeszti az immutátor interfészt, amelyik viszont csak olyan metódusokat deklarál, amelyek lekérdezésre, és még véletlenül sem objektum állapot változtatásra valók. Amikor pedig referenciát ad át egy ilyen objektumra a program egy metódusnak, akkor ha annak nem szabad megváltoztatni az objektum tartalmát, akkor az immutátor interfész típussal adja át, így (hacsak nagyon nem erőlködik) nem fog belepiszkítani a mi tiszta objektumunkba. Legfeljebb a vízből veszi ki a zoxigént.

Na de eléggé mélyen beástam magam ebbe a gondolati verembe, ki kellene poppolni innen, és mint a kismalac, nem várom meg a létrát, inkább dobok egy ellenőrizetlen kivételt, ami majd jól kivesz a veremből. Vissza a finálra.

Lokális változó is lehet final, objektum instancia változó is lehet final, és statikus változó is lehet finál. Most ne beszéljünk arról, hogy osztály is lehet final.

Lokális változók esetében főleg programozási szépség. Ez persze szubjektív. Viszont ha a metóduson belül létrehozzuk egy név nélküli osztály egy példányát, amelyikben hivatkozunk a metódusunk egy változójára, akkor annak a változónak final-kell lennie, deklaráció szempontjából is, és értéket is kellett kapnia, amikor az objektumot létrehozzuk. (Ez is megért egy misét.) Viszont a lokális változók a veremben vannak, minden szálnak saját verme van, egymás vermébe nem nyúlkálnak, ez az etika, mint a piszoárban. Ezért aztán a többszálúságot a lokális változók nem zavarják. Final, vagy fin ül, mindegy.

Nem így van azonban az osztály és az objektum változókkal! Ezeket, legyenek privát, protected, public vagy egyéb láthatóságúak (ismétlés: mi lehet még?) több szál is láthatja. Ha egy objektum változó nem final (és nem is volatile) értéket kap, akkor a lefordult kódnak szíve joga, hogy ezt az értéket berakja egy processzor regiszterbe, L1,2,3 cache-be és ne írja ki memóriába, amíg a szál azon a processzor(mag)on fut. Ő tudja, hogy mi a változó értéke, és nem jeleztük, hogy azon a szálon kívül mást is érdekelne, így akár amíg világ a világ és véget nem ér a JVM futása, akár sose nem is kerül ki az érték a memóriába, ahol egyébként az objektum van. Ha viszont az objektumváltozó final, akkor értéket kell, hogy kapjon, amikorra a konstruktor lefut, és ekkor kikerül memóriába is (vagy ha oda nem is, ez már processzor hardver kérdése, de annyi biztos, hogy a többi szál is a beállított értéket fogja látni).

A final objektumváltozó egy olyan “volatile” objektumváltozó, amelyik csak egyszer kap értéket a konstruktorban, és legkésőbb a konstruktor lefutásának a végére a többi szál számára is láthatóvá válik az értéke. (Meg ne kövezzetek! Tudom, hogy ez így pongyola megfogalmazás, de ha már kövezésről van szó, tudjátok meg, hogy ti mind egyéniségek vagytok!)

Persze nem, hanem még a volatile mezőknél is hatékonyabb. Azokat ugyanis minden egyes olvasásnál ellenőrizni kell, hogy valaki más nem módosította-e a tartalmukat, a final mezők viszont nem változhatnak, így eszébe sincs a kódnak kinézegetni a lassú memóriába, ha egy final változó értéke már bent van a processzor cache-ben.

A Java specifikáció külön meg is említi, hogy például a String minden mezője final, ezért, amikor létrejön egy új String objektum, akkor egy másik szálon (az egyik amelyik létrehozta, ehhez képest egy másikon) is használható az objektum, mert minden egyes mezője final tehát a másik szál is azt az értéket látja, amit a konstruktor létrehozott. (String témában javaslom ezt a bejegyzést először utána pedig ezt a bejegyzést, amely két bejegyzés igen mélyen belevájkál a String lelkivilágába). És hogy most már sokadik hete ne maradjunk kód nélkül, itt van egy mit ír ki:

import java.util.concurrent.atomic.AtomicInteger;

public class FinalTest {

	private static AtomicInteger counter = new AtomicInteger(0);

	static void waitFor(int n) {
		while (!counter.compareAndSet(n, n + 1)) {
		}
	}

	private static class C {
		final int x;
		int y;

		C(T other) {
			super();
			other.c = this;
			waitFor(0);
			waitFor(3);
			x = 1;
			y = 1;
			waitFor(4);
		}
	}

	private static class T extends Thread {
		public boolean constructor = false;
		public T other = null;
		public C c;

		@Override
		public void run() {
			if (constructor) {
				c = new C(other);
			} else {
				waitFor(1);
				System.out.println("x=" + c.x);
				System.out.println("y=" + c.y);
				waitFor(2);
				waitFor(5);
				System.out.println("x=" + c.x);
				System.out.println("y=" + c.y);
			}
		}
	}

	public static void main(String[] args) {
		T t = new T();
		t.constructor = true;
		t.other = new T();
		t.start();
		t.other.start();
		try {
			t.join();
			t.other.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
}

részletesebb magyarázat

Nos, mint többszálúság esetében ez szokott lenni, nem elegendő lefuttatni a programot, és megnézni, hogy mit ír ki. Mert amit kiír, az egy dolog, egy futáskor, egy gépen. Máskor, másik gépen esetleg mást ír ki. Az a tudomány, olyan programot írni, amelyik többszálú, de mégis garantálhatjuk, hogy minden gépen mindig ugyanazt produkálja. Ez a program nem ilyen, és ez lényeg benne. Amit ez kiír, az vagy

x=0
y=0
x=1
y=1

vagy éppen

x=0
y=0
x=1
y=0

Akinek a másodikat sikerül reprodukálnia, írja meg. Elvileg nem kizárt, és nagy terhelés esetén, amikor legkevésbé kellene, akkor fog megtörténni. No nem ez a kód, mert ez a kód, mint tapasztaltabbaknak egyből lejöhetett: nem production kód. Rövid, egybetűs, ugyan könnyen átlátható, de semmitmondó változónevek, e.printStackTrace és társai. Ami azonban a legfontosabb, hogy ez a kód direkt arra készült, hogy demonstrálja (sajnos a dolog természetéből adódóan kevés sikerrel), hogy mi az amit nem szabad. A javasolt eljárás ennek az ellentéte. A konstruktorokat úgy kell megírni, hogy azok ne adják ki a this értékét magukból, így az objektumhoz más szálból csak akkor lehessen hozzáférni, amikor a konstruktor már lefutott, és minden final mező értéke be lett állítva. Miért is tennénk mást? Mert észre sem vesszük. Készítünk egy factory-t, amelyik minden létrehozott objektumnak átadja magát, így minden objektumtól el lehet kérni, hogy melyik factory hozta létre. Aztán ahogy fejlődik a kód egyes osztályok még néhány dolgot be akarnak jegyezni magukról a factory-ban, a konstruktornak átadott factory objektumba visszahínak átadva paraméterként a this-t, és már itt is az antipattern. De azért működik a dolog, amíg egyszer egy távoli ügyfél malájföldön többszálú környezetben el nem kezdi használni, és Murfy-nek megfelelően crash. És ilyenkor tör elő a szupportos programozó szájából a másik Brian élete idézet: “Menjetek a …”

Vagyis a final egy rafinalt dolog, és nem csak szépészet!

Házi feladat: Gondold át, hogy mi a helyzet a static final mezőkkel. Gondolj arra, hogy az osztályok maguk is objektumok.

UPDATE

Kofa felhívta a figyelmemet, hogy a waitFor metódusban a meghívott compareAndSet metódus változtat egy volatile változót, aminek a hatására az y változó megváltozott értéke is garantáltan elérhetővé válik az összes szál számára. Így ez a példaprogram nem jó, soha nem fordulhat elő a

x=0
y=0
x=1
y=0

eredmény.

3 responses to “final, fin ül (update 2013-02-11 14:02)

  1. hron84 február 6, 2013 8:54 du.

    No offense, csak tipp: add fel a wordpress kod pluginjevel valo kuzdelmet, es hasznalj Gistet. Azt siman csak be lehet embeddalni a posztba, es akkor nem lesznek ilyen csunya ampokok meg kvotok. Rettento csunya kulso szemlelonek – meg ha ertheto is.

    • v február 7, 2013 8:45 de.

      No problem. Gondoltam már rá. Így egyszerűbb szerkeszteni, és az XML exportban minden benne van, az összes információ.

      Akkor rontja el, ha a teljes képernyős módot választom szerkesztésnél, illetve a mobil kliens. Az utóbbit most vettem észre. Sajnos publikálni egy elkészült cikket mobilról csak úgy lehet, hogy megnyitom szerkesztésre, és ilyenkor ezeket a kódokat a source részekben átírja. Ha még egyszer megnyitom, akkor újra és újra egyre többször escapelve.

      Nem szabad mobilról szerkeszteni és publikálni. Most már ezt is tudom.

  2. tamasrev február 10, 2013 5:12 du.

    Fhu, ez a kód remekül demonstrálja, hogy mit nem szabad. Másodjára olvasom el, és még mindig csak kapisgálom. Megyek is a másik cikkre, és elolvasom részletesen.

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s

%d blogger ezt kedveli: