Java Happens-Before и Thread Safety

Предположим, что у меня есть класс, который обертывает HashMap следующим образом:

public final class MyClass{

     private final Map<String, String> map;

     //Called by Thread1
     public MyClass( int size ){
         this.map = new HashMap<String, String>( size );
     }

     //Only ever called by Thread2
     public final String put( String key, String val ){
        return map.put( key, value );
     }

     //Only ever called by Thread2
     public final String get( String key ){
        return map.get( key );
     }

     //Only ever called by Thread2
     public final void printMap(  ){
       //Format and print the contents of the map
     }

}

Этот класс инициализируется через "Thread1". Тем не менее, put, get, printMap и другие операции только когда-либо, вызываемые "Thread2".

Я правильно понимаю, что этот класс является потокобезопасным как:

  • Поскольку ссылка на карту объявлена ​​окончательной, все остальные потоки будут видеть начальное состояние карты (происходит-до этого установлено).

  • Так как put/get/printMap/etc вызывается только Thread2, нет необходимости в взаимном исключении.

Спасибо

Ответ 1

Итак, то, что вы просите, является правильным предположением. Вам не нужно делать это потокобезопасным, если вы можете гарантировать, что он используется только таким образом. Вы не можете передать наполовину построенный объект в java, поэтому "Конструктор может не завершиться" невозможен.

Итак, если вы делаете

new Thread(new Runnable() {

    public void run() {
          final MyClass instance = new MyClass(10);
          new Thread(new Runnable() {
               public void run() {
                    instance.put("one", "one");
                      ....
               }

           }).start();
     }

}).start();

Вы в порядке:) Это то, что вы описали, созданное Thread1, но используемое только Thread2. Невозможно совпадение потока с самим собой.

Thread-Safe - это другое определение, в котором скомпонованный объект может безопасно взаимодействовать несколькими потоками. В описанном вами случае этого сценария не происходит, поскольку у вас по существу есть поток, который строит, и другой, который манипулирует.

Ответ 2

Это немного сложно ответить, потому что JLS не содержит понятия класса, который является потокобезопасным — все это указывает на взаимосвязь между действиями (например, запись в поле, чтение из поля и т.д.).

Тем не менее, по большинству определений этот класс не является потокобезопасным - он содержит расы данных, вызванные несинхронизированным доступом к не-потокобезопасной карте. Тем не менее, ваше использование этого потока является потокобезопасным, потому что вы благополучно публикуете this.map в Thread 2 после построения, и в этот момент this.map доступен только один поток одним потоком, и в этом случае безопасность потоков не является проблемой.

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

Аналогично, ваш класс не является потокобезопасным, но похоже, что это может и не быть.

Ответ 3

Если вы согласитесь на свое определение того, что произойдет, тогда он действительно будет потокобезопасным. То есть, только нить-2 будет помещаться и получать с карты.

Так как Map объявлен окончательным, вы устанавливаете связь между событиями перед записью потока-1 и чтением потока-2.

a) Поскольку ссылка на карту объявлена ​​окончательной, все остальные потоки будет видеть начальное состояние карты (происходит-раньше установлено).

Да. Указывается пустой HashMap с size.

b) Так как put/get/printMap/etc вызывается только Thread2, там нет необходимости в взаимном исключении.

Да, хотя этот тип логики обычно пугает меня на практике:)

Ответ 4

Во-первых: Инициализация final и volatile поля в openJDK всегда появляются перед возвратом ссылки на объект конструктором. Переупорядочение не произошло. Тогда построение объекта является потокобезопасным.

Во-вторых: если методы put, get, printMap вызывается в Thread2, то исключение mutuall не нужно.

В ваш случай класса использования безопасен.

Ответ 5

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

Я могу представить два возможных способа доступа Thread2 к экземпляру MyClass.

  • Оба Thread1 и Thread2 запускаются независимо. В этом случае он не является потокобезопасным, потому что Thread2 может вызвать put/get/printMap/etc до того, как завершится конструктор, работающий в Thread1, в результате чего появится NPE. Экземпляр MyClass, к которому обратился Thread2, может быть нулевым в зависимости от того, как Thread2 обращается к нему. Если это общий экземпляр, когда Thread2 обращается к нему, он может быть нулевым, если конструктор MyClass не завершил выполнение в Thread1.
  • Thread1 создает экземпляр и передает этот экземпляр Thread2. В этом случае он является потокобезопасным, но это фактически не имеет значения, потому что нет многопоточного доступа к этому экземпляру, потому что Thread2 должен ждать передачи экземпляра.

Итак, в описанной ситуации ответ на самом деле зависит не от реализации класса, а от того, как его общий экземпляр. И в худшем случае (путь 1) он не является потокобезопасным.