tifyty

pure Java, what else ?

Mit csinál ez a kód

Ez a post nem különálló poszt, hanem a posztot egészíti ki. Abban a posztban szerepel egy mintaprogram, de a magyarázat egy kicsit hosszú ahhoz, hogy abban a bejegyzésben szerepeljen, ezért itt van külön. A kódot ide is bemásoltam darabokra vágva magyarázatokkal:

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)) {
		}
	}

Amit a kód elején látunk az egy atomi integer, amit arra használunk, hogy a később elindított két szál közül mindig csak az egyik fusson, és így pontosan tudjuk, hogy mi milyen sorrendben történik. A való életben persze éppen nem ez történik, azért indítunk el szálakat, hogy azok párhuzamosan fussanak, itt viszont pont azt akarjuk demonstrálni, hogy mi történik akkor, ha a kód futása során az egyik szál eléri a másik szál által félig létrehozott objektumot.

A waitFor metódus a program futás során tervezett n-edik állapotra vár, és ha ez elérkezett (a másik szál módosította a számlálót), akkor megnöveli a számláló értékét és visszatér. Itt kell megjegyezni, hogy az egyszerűség kedvéért itt csak egy üres while ciklus van, ami igazi kódban elfogadhatatlan. Először is, kellene bele legalább egy sleep, hogy ne egye meg az összes CPU időt azon a magon amelyiken fut, másodszor nem foglalkozik azzal, hogy a szál kap-e interruptot, harmadrészt egy ilyen pollozás nem hatékony, inkább wait és notify, notifyAll metódusokat kell használni, és végül, de elsősorban még azokat sem, hanem valami olyan megoldást, ami ennél magasabb szintű, mondjuk egy aszinkron queue.

A main előtt van két osztályunk. Az egyik a majd később elindítandó két szál kódja, (ez a T), a másik pedig az az osztály, amelyik elköveti azt a kerülendő disznóságot, hogy a konstruktor futása közben kiadja a this referenciát (ez a C).

	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);
			}
		}
	}

A main mind a két szálat létrehozza, az egyiknek azt mondja, hogy legyen ő az, amelyik meghívja a C() konstruktort, a másik meg majd hozzányúl, és nézi, hogy mi az értéke a változóknak. A konstruktort elindító szál fog érdemi dolgot csinálni előbb, a másik első dolga az, hogy megálljon, és várjon arra, hogy a konstruktor őt továbbengedje (waitFor(1)).

A konstruktor beállítja az argumentumként megkapott szálobjektumban az other mezőt, hogy a másik szál hozzá tudjon férni az objektumhoz, ami éppen most jön létre, majd a waitFor(0) hívással nem vár, mert eleve a 0 állapotból indul a program, ellenben növeli vele a számláló értékét, és így a másik szál kódjában a waitFor(1) vissza fog térni, és hogy a konstruktor ne csináljon semmit, amíg az el nem végzett annyit, amit el kell végeznie, ezért elkezd várni a 3-as állapotra.

A másik szál fogja, és kiírja a még nem teljesen létrehozott embrió objektum x és y mezőjének értékét. Mivel ezek még nem kaptak értéket, ezért mind a kettő értéke definíció szerint a default érték. Ezt követően tovább engedi a konstruktort futtató szálat, és vár arra, hogy az befejezze, amit be kell fejeznie. A konstruktor pedig értéket ad a két változónak, majd ezt jelzi a másik szálnak, aki ismét kiírja, most már a beállított értéket a final x mező esetében, az y esetében meg vagy a default-ot, vagy a beállított értéket.

	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();
		}
	};
}

A main működése triviális.

3 responses to “Mit csinál ez a kód

  1. Kofa február 11, 2013 3:30 du.

    Az AtomicInteger számlálóban:
    private volatile int value;
    Ennek írása és olvasása szinkronizációt okoz; az írás előtti nem volatile írások (beleértve pl. c, c.x, c.y) írását láthatóvá válnak a mező olvasása után. [Már annak is gyanút keltő, hogy miért látja a 2. szál c értékét, ha nincs szinkronizáció a két szál között.]

    JLS 17. fejezet.

    Every execution has a synchronization order. A synchronization order is a total order over all of the synchronization actions of an execution. For each thread t, the synchronization order of the synchronization actions (§17.4.2) in t is consistent with the program order (§17.4.3) of t.
    A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where “subsequent” is defined according to the synchronization order).

    Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second.
    If we have two actions x and y, we write hb(x, y) to indicate that x happens-before y.
    If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).
    If an action x synchronizes-with a following action y, then we also have hb(x, y).

    A másik probléma, hogy magának a System.out.println-nek is vannak szinkronizációs mellékhatásai (ha assert-re cserélem, sem működik, c.y mindig egyezni fog c.x-szel a fenti volatile írás-olvasás miatt).

  2. Visszajelzés:Tudásteszt, multi thread 1 « tifyty

  3. Kofa február 13, 2013 6:42 du.

    Akkor amit már e-mailben is beszéltünk, ezért látszik a nem volatile “foo” változónak a volatile “v” változó írása előtt elvégzett írása, ha ha “foo”-t “v” olvasása után olvassuk (húúú….):

    1) If x and y are actions of the same thread and x comes before y in program order, then hb(x, y). (http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5)
    2) A write to a volatile variable v synchronizes-with all subsequent reads of v by any thread (where “subsequent” is defined according to the synchronization order). If an action x synchronizes-with a following action y, then we also have hb(x, y). -> A write to a volatile field happens-before every subsequent read of that field. (http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.4)
    3) If hb(x, y) and hb(y, z), then hb(x, z). (http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5)

    int foo;
    volatile boolean v;

    Thread-1:
    a: foo = 1;
    b: v = true;

    Thread-2:
    c: boolean copyV = v;
    d: int copyFoo = foo;

    Itt ugye 4 “action” (http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.2) van (a, b, c, d).

    hb(a,b), mert (1)
    hb(c,d), mert (1)
    hb(b,c), mert (2)
    hb(a,c) mert hb(a,b) és hb(b,c)->hb(a,c) (3) miatt
    hb(a,d), mert hb(a,c) és hb(c,d) -> hb(a,d) (3) miatt.

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: