Multithreading - blockweise Synchronisieren

In meinem System werden auf einem Thread mehrere Berechnungen von Objekten ausgeführt und die Ergebnisse bereitgestellt. Die Ergebnisse werden dann asynchron aus anderen Threads immer wieder abgefragt.
Das Problem: Manche Objekte stehen in Abhängigkeit zueinander.

Ändert Objekt A sein Zahlenergebnis a, dann kann das unter Umständen auch einen Einfluss auf das Ergebnis b von Objekt B haben.
Jetzt muss verhindert werden, dass ein anderer Thread das neuberechnete Ergebnis von Objekt A bezieht, aber noch das alte von Objekt B bekommt, weil B zeitlich noch nicht in der Lage war sich neu zu berechnen.

Selbst wenn ich die Ergebnisse buffere und zeitgleich die Variablen austausche kann es pasieren das zwischen
a = bufferedA
und
b = bufferedB
a und b gelesen werden.

Ich hoffe ihr könnt mir folgen, ich suche eine Möglichkeit über Objekte hinweg Daten zu synchronisieren, ohne synchronized und ohne die Objektstruktur A und B aufzubrechen.

Ist das Problem nicht eher, dass zwischen
a = newA
und
b = newB
jemand sich beides abholt, und damit schon das neue a aber eben noch das “alte” b holt?

Ansonsten klingt das, als bräuchte man genauere Informationen, was A und B sind.

Landen die Ergebnisse in einer Queue? Oder in mehreren? D.h. sind das ganz klassische Producer-Consumer-BlockingQueues? Läuft das zyklisch? Ansonsten: Wodurch wird das Abholen (durch die anderen Threads) getriggert? Der eine muss ja zumindest schonmal wissen, dass überhaupt ein b da ist…?!

Gibt es jemanden, der such NUR a oder NUR b abholt, und den es nicht interessiert, ob das b vielleicht veraltet ist? Ansonten könnte der “Producer” eben einmal a und einmal (a,b) in die queue legen, und wer auch immer sich b abholt, bekommt auch automatisch DAS a, das dazugehört.

Genau

Die Objekte stellen shared Memory dar. Ein dedizierter Thread schreibt dort rein, die anderen lesen davon - zyklisch.
Die Threads haben also jeweils eine eigene Liste der Objekte.

Ein spezifisches Beispiel wäre ein Scenegraph. Zwei Threads (Renderer (lesen) und Logik (schreiben)). Ändert sich die Position von Objekt A (Auto), ändert sich auch die Position vom Subobjekt B (Bereifung) relativ zu A.
Läuft der Renderthread durch den Scenegraph, und die Position von Auto wurde gerade neuberechnet aber die Position er Bereifung noch nicht… dann sieht das im fahrenden Zustand entsprechend zitternd aus.

Nun, bisher klingt das so unspezifisch, dass ich kaum mehr machen könnte, als händewedelnd irgendwelche Begriffe zu nennen, aber OK: Das heißt dieser “shared memory” ist unsynchronisiert (und eben nicht sowas wie eine BlockingQueue). Als pseudocode-Beispiel:

Integer shared[] = new Integer[2];

// Der einzige Producer
Thread producer = new Thread() {
    int counter = 0;
    while (true) { 
        shared[counter%2] = counter++;
    }
}

// Einer von mehreren Consumern...
Thread consumer0 = new Thread() {
    int counter = 0;
    while (true) { 
        if (consumingWasTriggeredFromOutside()) {
            int sum = shared[0] + shared[1];
            assert(isOdd(sum));
        }
    }
}

Soweit:

  • Der Producer macht permanent und unkontrolliert beliebige Updates im shared memory
  • Jeder Consumer ist “von außen” getriggert, zu beliebigen, nicht kontrollierbaren Zeitpunkten
  • Ein Consumer macht Annahmen über den Inhalt des shared memories (im dummy-Beispiel eben, dass die Summe der Einträge ungerade ist)

Wenn das grob so ist, würde mir (wenn man dicke Locks und Synchronisation vermeiden will) kaum etwas anderes einfallen, als irgendeine Form von double-buffering…

Integer shared[] = new Integer[2];
Integer sharedBack[] = new Integer[2];

Thread producer = new Thread() {
    int counter = 0;
    while (true) { 
        sharedBack[counter%2] = counter++;
        if ((counter%2)==sharedBack.length-1) {
            temp = shared;
            shared = sharedBack;
            sharedBack = temp;
         }
    }
}

D.h. wenn alle Elemente bearbeitet wurden und ein konsistenter Zustand erreicht wurde, wird ein atomarer (!) swap der beiden Shared Memories gemacht.

Ob man diesen “konsistenten Zustand” definieren oder erkennen kann, ist eine konzeptuelle Frage. (Das atomare swappen dann eher eine technische).