2015-12-31 3 views
4

Я смущен о безопасном обмене массивами между потоками в Java, в частности, заборами памяти и ключевым словом synchronized.Выполняет ли синхронизированный блок полный забор памяти для массивов?

Это Q & А полезно, но не отвечает на все мои вопросы: Java arrays: synchronized + Atomic*, or synchronized suffices?

Ниже приведен пример кода, чтобы продемонстрировать эту проблему. Предположим, что существует пул рабочих потоков, который заполняет SharedTable методом add(...). После завершения всех рабочих потоков последний поток считывает и сохраняет данные.

Пример кода для демонстрации проблемы:

public final class SharedTable { 

    // Column-oriented data entries 
    private final String[] data1Arr; 
    private final int[] data2Arr; 
    private final long[] data3Arr; 
    private final AtomicInteger nextIndex; 

    public SharedTable(int size) { 
     this.data1Arr = new String[size]; 
     this.data2Arr = new int[size]; 
     this.data3Arr = new long[size]; 
     this.nextIndex = new AtomicInteger(0); 
    } 

    // Thread-safe: Called by worker threads 
    public void addEntry(String data1, int data2, long data3) { 
     final int index = nextIndex.getAndIncrement(); 
     data1Arr[index] = data1; 
     data2Arr[index] = data2; 
     data3Arr[index] = data3; 
    } 

    // Not thread-safe: Called by clean-up/joiner/collator thread... 
    // after worker threads are complete 
    public void save() { 
     // Does this induce a full memory fence to ensure thread-safe reading of 
     synchronized (this) { 
      final int usedSide = nextIndex.get(); 
      for (int i = 0; i < usedSide; ++i) { 
       final String data1 = data1Arr[i]; 
       final int data2 = data2Arr[i]; 
       final long data3 = data3Arr[i]; 
       // TODO: Save data here 
      } 
     } 
    } 
} 

В примере кода выше, также может быть реализован с использованием Atomic*Array, который действует как «массив значений летучих/ссылок».

public final class SharedTable2 { 

    // Column-oriented data entries 
    private final AtomicReferenceArray<String> data1Arr; 
    private final AtomicIntegerArray data2Arr; 
    private final AtomicLongArray data3Arr; 
    private final AtomicInteger nextIndex; 

    public SharedTable2(int size) { ... } 

    // Thread-safe: Called by worker threads 
    public void addEntry(String data1, int data2, long data3) { 
     final int index = nextIndex.getAndIncrement(); 
     data1Arr.set(index, data1); 
     ... 
    } 

    // Not thread-safe: Called by clean-up/joiner/collator thread... 
    // after worker threads are complete 
    public void save() { 
     final int usedSide = nextIndex.get(); 
     for (int i = 0; i < usedSide; ++i) { 
      final String data1 = data1Arr.get(i); 
      final int data2 = data2Arr.get(i); 
      final long data3 = data3Arr.get(i); 
      // TODO: Save data here 
     } 
    } 
} 
  1. Является SharedTable поточно-(и кэш-когерентной)?
  2. SharedTable (много?) Более эффективен, так как требуется только один забор памяти, тогда как SharedTable2 вызывает забор памяти для каждого вызова до Atomic*Array.set(...)?

Если это помогает, я использую Java 8 на 64-разрядном оборудовании x86 (Windows и Linux).

+0

Комментарий к методу 'public void addEntry (..)' как в 'SharedTable', так и' SharedTable2' говорит, что этот метод является потокобезопасным. Я предполагаю, что вы могли бы синхронизировать объекты 'SharedTable' или' SharedTable2' в рабочих потоках при вызове метода public void addEntry (..) '. Не так ли? –

ответ

3

Нет, SharedTable не является потокобезопасным. Происшествие-прежде гарантируется только в том случае, если вы прочитали из синхронизированного блока то, что было написано из синхронизированного блока с использованием той же блокировки.

Поскольку записи выполнены из синхронизированного блока, JMM не гарантирует, что записи будут видны нитью считывателя.

+0

Очень информативно! Последующий вопрос: если я могу вызвать полный забор памяти (через 'sun.misc.Unsafe'), вместо использования« синхронизированного »блока будет ли« SharedTable »быть потокобезопасным? – kevinarpe

+1

Я бы не использовал Unsafe. Я бы использовал атомный массив, который избегает использования небезопасных, а не предполагаемых классов использования, и делает цель четкой в ​​коде. Это должно быть достаточно быстро. Если это не так, и если вы доказали, что проблема исходит из этой части, тогда начните оптимизацию. –

+0

Я возвращаюсь к этому ответу, потому что он настолько полезен, но настолько плотен, если вы не очень хорошо знакомы с концепцией * происходит до * в модели памяти Java [JMM]. Эта ссылка помогает понять больше: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility – kevinarpe