Многопоточное условие Java для разделяемого массива char - PullRequest
0 голосов
/ 30 мая 2018

Предположим, у меня есть следующее:

char[] shared = new char[]{'a', 'b'};
ExecutorService exec = Executors.newCachedThreadPool();
Future<?> f1 = exec.submit(() -> shared[0] = 'A');
Future<?> f2 = exec.submit(() -> shared[1] = 'B');
f1.get(); f2.get();
System.out.println(shared);

Я ожидаю, что это будет обычно печатать:

'AB'

Но возможно ли это для печати:

'aB'

или

'bA'

Интересно, если shared не является энергозависимым, один поток может, сохраняя свои изменения в соответствующем элементе char, сохранить соседние байты, которыеустареть в своем локальном кеше процессора и растоптать изменения, сделанные другим потоком.У меня есть чувство, что ответ «нет», потому что я думаю, что это нарушило бы модель памяти Java, которая гласит, что всякий раз, когда поток присоединяется, все действия, которые он выполняет, выполняются до соединения.Но если это так, то мне интересно, как это предотвратить?Насколько я понимаю, когда байты сбрасываются, вся строка кэша сбрасывается, и я предполагаю, что массив char [] будет иметь несколько элементов, расположенных в одной строке кэша.

Ответы [ 2 ]

0 голосов
/ 12 июля 2018

Это было указано в Спецификации языка Java , а именно:

17.6.Word Tearing

Одним из соображений реализации виртуальной машины Java является то, что каждое поле и элемент массива считаются различными;Обновления одного поля или элемента не должны взаимодействовать с чтениями или обновлениями любого другого поля или элемента.В частности, два потока, которые обновляют соседние элементы байтового массива отдельно, не должны мешать или взаимодействовать и не нуждаются в синхронизации для обеспечения последовательной согласованности.

Некоторые процессоры не обеспечивают возможность записи в один байт.Было бы незаконно реализовывать обновления байтового массива на таком процессоре, просто читая все слово, обновляя соответствующий байт, а затем записывая все слово обратно в память.Эта проблема иногда называется разрывом слов, и для процессоров, которые не могут легко обновить один байт в отдельности, потребуется другой подход.

В худшем случае это будет означать выполнение таких обновлений массива синструкция CAS или аналогичная, чтобы гарантировать, что промежуточное обновление не произошло.Но, насколько мне известно, архитектура x86 / x64 не подвержена этому влиянию, поскольку в ней есть инструкции по изменению элементов меньшего размера, и она будет обрабатывать аннулирование строки кэша другого ядра ЦП, чтобы другие ядра ЦП извлекли измененные элементы.данные перед выполнением собственного обновления.Конечно, это все еще медленнее, чем два ядра ЦП, выполняющие обновления на разных строках кэша.

0 голосов
/ 31 мая 2018

Нет, это невозможно.Вы можете убедиться в этом сами, если посмотрите на сгенерированный байт-код.

// lambda synthetic method
private static synthetic lambda$main$0([C)Ljava/lang/Character; throws java/lang/Exception
    L0
    LINENUMBER 11 L0
    ALOAD 0           // Loads reference of array "shared"
    ICONST_0          // Load index 0 onto the stack
    BIPUSH 65         // 65 = letter A in unicode
    DUP_X2            
    CASTORE           // Consumes reference, index, and value from stack
    INVOKESTATIC java/lang/Character.valueOf (C)Ljava/lang/Character;
    ARETURN
    MAXSTACK = 4
    MAXLOCALS = 1

Опкод CASTORE принимает только 3 аргумента, ссылку на массив, индекс и значение для сохранения в массиве символов.Нет никаких указаний на то, что присвоение массива будет пытаться продублировать массив (или любые соседние значения массива в этом отношении), поскольку, по сути, нет места, где такое преобразование было бы полезно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...