2016-05-31 4 views
3

Streams und fork-join bieten beide Funktionen zum Parallelisieren von Code, der auf Arrays zugreift. Zum Beispiel wird Arrays.parallelSetAll weitgehend durch die folgende Zeile implementiert:Wie greifen Streams/fork-join auf Arrays sicher zu?

IntStream.range(0, array.length).parallel() 
    .forEach(i -> { array[i] = generator.applyAsLong(i); }); 

Auch die documentation von RecursiveAction, einen Teil des Gabel-Join Framework enthält das folgende Beispiel:

static class SortTask extends RecursiveAction { 
    final long[] array; final int lo, hi; 
    ... 
    void merge(int lo, int mid, int hi) { 
     long[] buf = Arrays.copyOfRange(array, lo, mid); 
     for (int i = 0, j = lo, k = mid; i < buf.length; j++) 
      array[j] = (k == hi || buf[i] < array[k]) ? 
       buf[i++] : array[k++]; 
    } 
} 

Schließlich parallele Ströme erstellt aus Arrays greifen auf die Arrays in mehreren Threads zu (der Code ist zu komplex, um hier zusammengefasst zu werden).

Alle diese Beispiele scheinen aus Arrays zu lesen oder in sie zu schreiben, ohne Synchronisation oder andere Speicherbarrieren (soweit ich das beurteilen kann). Wie wir wissen, sind vollständig Ad-hoc-Multithread-Array-Zugriffe unsicher, da es keine Garantie gibt, dass ein Lesen ein Schreiben in einem anderen Thread widerspiegelt, es sei denn, es gibt eine Vor-Vor-Beziehung zwischen dem Lesen und dem Schreiben. Tatsächlich wurden die Klassen Atomic...Array speziell erstellt, um dieses Problem zu beheben. Da jedoch jedes obige Beispiel in der Standardbibliothek oder in der zugehörigen Dokumentation enthalten ist, nehme ich an, dass sie korrekt sind.

Kann jemand bitte erklären, welcher Mechanismus die Sicherheit der Arrayzugriffe in diesen Beispielen garantiert?

+1

Beachten Sie, dass zwei Threads nicht auf den _Same_Array-Einträge arbeiten; sie überschneiden sich nicht. –

+0

@LouisWasserman Egal, wie sieht der Thread, der 'parallelSetAll' aufruft oder der' SortTask' aufruft, die darin gemachten Schreibvorgänge? –

Antwort

6

Kurze Antwort: Partitionierung.

Das JMM ist in Bezug auf den Zugriff auf Variablen definiert. Variablen enthalten statische Felder, Instanzfelder und Array-Elemente. Wenn Sie Ihr Programm so anordnen, dass der Thread T0 der einzige Thread ist, der auf das Element 0 eines Arrays zugreift, und T1 genauso der einzige Thread ist, der auf das Element 1 eines Arrays zugreift, dann ist jedes dieser Elemente tatsächlich threadbegrenzt Kein Problem - die JMM-Programmbestellungsregel kümmert sich um Sie.

Parallele Ströme bauen auf diesem Prinzip auf. Jede Aufgabe arbeitet an einem Segment des Arrays, an dem keine andere Aufgabe arbeitet. Dann müssen wir lediglich sicherstellen, dass der Thread, der eine Aufgabe ausführt, den Ausgangszustand des Arrays sehen kann, und der Konsument des Endergebnisses kann die Ansicht "Angepasst durch Aufgabe" des entsprechenden Abschnitts des Arrays sehen . Diese werden durch Synchronisationsaktionen, die in die Implementierung der parallelen Datenstrom- und FJ-Bibliotheken eingebettet sind, leicht angeordnet.

+0

Brian, danke für deine Antwort. Aber du beschönigst das Herz davon. Welche Synchronisationsaktionen können die erforderliche Sichtbarkeitssemantik erreichen? Sie können nicht auf einem einzelnen Array-Element synchronisieren oder es als flüchtig markieren, und die Synchronisierung auf dem gesamten Array würde die Parallelität verhindern. Ich glaube, 'java.util.concurrent.locks' haben dieselbe Semantik. –

+0

Sie müssen _in dem Array-Element_ nicht synchronisieren, um seine Sichtbarkeit zu gewährleisten. Sie brauchen lediglich eine Grenze zwischen dem Schreiber und dem Leser. Zusätzlich zu den offensichtlichen Mechanismen ("synchronisiert" und "flüchtig") liefern viele gleichzeitige Bibliotheksabstraktionen Konsistenzgarantien. Zum Beispiel sagt die 'Future'-Klassenspezifikation:" Aktionen, die von der asynchronen Berechnung ausgeführt werden, _happen-vor_ Aktionen, die dem entsprechenden 'Future.get()' in einem anderen Thread folgen. " Ähnliche Garantien werden von 'BlockingQueue',' ConcurrentHashMap', 'ForkJoinTask', etc. gegeben. –

+0

Sie haben Recht und ich wollte nicht" synchronisiert "und" flüchtig "implizieren sind die * einzigen * Möglichkeiten, die Sichtbarkeit zu garantieren. Ich vermute, ich nehme an, dass Bibliotheksklassen, die die von Ihnen erwähnten Gleichzeitigkeitsgarantien bereitstellen, in Form bestimmter primitiver Elemente implementiert sind, und ich bin mir nicht bewusst, dass ein Primitiv die gewünschte Nebenläufigkeit bietet.Zum Beispiel rufen 'Atomic * Array' Klassen auf' Unsafe.put ... 'auf, aber die Beispiele im OP greifen direkt auf die Arrays zu. Ich habe diese Frage gestellt, nachdem ich den Code untersucht habe, aber ich werde ihn weiter durchlesen. –

Verwandte Themen