2012-06-12 2 views
14

Должны ли мы объявить частные поля как volatile, если instanced используются в нескольких потоках?java: `volatile` частные поля с геттерами и сеттерами

В Effective Java, есть пример, когда код не работает без летучий:

import java.util.concurrent.TimeUnit; 

// Broken! - How long would you expect this program to run? 
public class StopThread { 
    private static boolean stopRequested; // works, if volatile is here 

    public static void main(String[] args) throws InterruptedException { 
     Thread backgroundThread = new Thread(new Runnable() { 
      public void run() { 
       int i = 0; 
       while (!stopRequested) 
        i++; 
      } 
     }); 
     backgroundThread.start(); 
     TimeUnit.SECONDS.sleep(1); 
     stopRequested = true; 
    } 
} 

Объяснения говорит, что

while(!stopRequested) 
    i++; 

оптимизирован что-то вроде этого:

if(!stopRequested) 
    while(true) 
     i++; 

поэтому дальнейшие модификации stopRequested не видны по фоновому потоку, так что он навсегда зацикливается. (Кстати, этот код завершается без volatile на JRE7.)

Теперь рассмотрим этот класс:

public class Bean { 
    private boolean field = true; 

    public boolean getField() { 
     return field; 
    } 

    public void setField(boolean value) { 
     field = value; 
    } 
} 

и нить следующим образом:

public class Worker implements Runnable { 
    private Bean b; 

    public Worker(Bean b) { 
     this.b = b; 
    } 

    @Override 
    public void run() { 
     while(b.getField()) { 
      System.err.println("Waiting..."); 
      try { Thread.sleep(1000); } 
      catch(InterruptedException ie) { return; } 
     } 
    } 
} 

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

public class VolatileTest { 
    public static void main(String [] args) throws Exception { 
     Bean b = new Bean(); 

     Thread t = new Thread(new Worker(b)); 
     t.start(); 
     Thread.sleep(3000); 

     b.setField(false); // stops the child thread 
     System.err.println("Waiting the child thread to quit"); 
     t.join(); 
     // if the code gets, here the child thread is stopped 
     // and it really gets, with JRE7, 6 with -server, -client 
    } 
} 

Я думаю, что из-за публичного сеттера, c ompiler/JVM никогда не должен оптимизировать код, который вызывает getField(), но this article говорит, что существует некоторый шаблон «Volatile Bean» (шаблон № 4), который должен применяться для создания изменяемых потокобезопасных классов. Обновление: Возможно, эта статья применима только к IBM JVM?

Вопрос: какая часть JLS явно или неявно говорит, что частные примитивные поля с общедоступными getters/setters должны быть объявлены как volatile (или им это не обязательно)?

Извините за длинный вопрос, я попытался подробно объяснить проблему. Дайте мне знать, если что-то неясно. Благодарю.

+0

Это не похоже на то, что вам нужно это поле, чтобы отменить поток, вы можете использовать прерванный флаг в потоке. –

+1

@NathanHughes, эти классы - всего лишь минимальные примеры, фактический код отличается, и там не требуется прерывание потока. – khachik

ответ

4

Прежде чем ответить на ваш вопрос, я хочу обратиться к

Кстати, этот код завершается без летучих на JRE7

Это может измениться, если вы должны были развернуть те же приложения с разными аргументами во время выполнения. Подъем не обязательно является реализацией по умолчанию для JVM, поэтому он может работать в одном, а не в другом.

Чтобы ответить на ваш вопрос есть ничто не мешает компилятор Java от выполнения вашего последнего примера, как так

@Override 
public void run() { 
    if(b.getField()){ 
     while(true) { 
      System.err.println("Waiting..."); 
      try { Thread.sleep(1000); } 
      catch(InterruptedException ie) { return; } 
     } 
    } 
} 

Это еще последовательно в соответствии и, таким образом, поддерживает гарантии в Java - вы можете прочитать специфически 17.4.3:

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

Набор действий является последовательно согласованным, если все действия происходят в общем порядке (порядок выполнения), что согласуется с программой порядком, и, кроме того, каждый прочитал г переменной V видит значение , написанного напишите w в v так, что:

Другими словами - до тех пор, пока поток увидит чтение и запись поля в том же порядке, независимо от порядка упорядочения/компиляции, будет считаться последовательным.

9

Вопрос: какая часть JLS явно или неявно говорит о том, что частные примитивные поля с общедоступными геттерами/сеттерами должны быть объявлены нестабильными (или им не обязательно)?

Модель памяти JLS не заботится о геттерах/сеттерах. Они не являются операционными системами с точки зрения модели памяти - вы также можете получать доступ к публичным полям. Обтекание логической точки за вызовом метода не влияет на видимость его памяти. Ваш последний пример работает исключительно по везению.

Следует ли объявлять частные поля как изменчивые, если инстанс используется в нескольких потоках?

Если класс (боб) должна использоваться в многопоточной среде, вы должны каким-то образом принять это во внимание. Создание частных полей volatile - один из подходов: он гарантирует, что каждому потоку гарантировано будет отображаться последнее значение этого поля, а не что-либо кэшированное/оптимизированное от устаревших значений. Но это не решает проблему atomicity.

The article you linked to относится к любой JVM, которая придерживается спецификации JVM (на которую опирается JLS). Вы получите различные результаты в зависимости от поставщика JVM, версии, флагов, компьютера и ОС, количества раз, когда вы запускаете программу (оптимизация HotSpot часто срабатывает после 10000-го запуска) и т. Д., Поэтому вы действительно должны понимать спецификацию и тщательно придерживаться к правилам для создания надежных программ. Экспериментировать в этом случае - это плохой способ узнать, как все работает, потому что JVM может вести себя так, как хочет, до тех пор, пока он попадает в спецификацию, и большинство JVM действительно содержат нагрузки всех видов динамических оптимизаций.

+0

спасибо. Установка логической переменной (или любой другой примитивной переменной, кроме long/double) является атомной операцией. Мой вопрос в том, как JSL определяет поведение кода в этом случае. Или как это не определяет, если ясно, что я имею в виду. – khachik

+0

JLS не определяет ничего конкретного в вашем случае. Боб - это просто переменные за вызовами методов, применяется [модель памяти] (http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4). Вызов методов не является операцией с точки зрения модели памяти. По атомарности я подразумеваю, что ваш bean-компонент может оказаться в несогласованном состоянии, если у вас есть поля, которые логически зависят друг от друга: например, время начала должно быть до «конца» или что-то в этом роде - оно не гарантируется, если у вас нет механизм для изменения этих двух полей атомарно. –

+0

Кстати, записи и чтения значений 'volatile'' long' и 'double' всегда являются атомарными. См. JLS 17.7. –

4

Нет, этот код является таким же неправильным. Ничто в JLS не говорит, что поле должно быть объявлено как изменчивое состояние. Однако, если вы хотите, чтобы ваш код работал правильно в многопоточной среде, вы должны соблюдать правила видимости. энергозависимые и синхронизированные - это два из основных средств для правильного отображения данных по потокам.

Что касается вашего примера, то сложность написания многопоточного кода заключается в том, что многие формы неправильного кода отлично работают при тестировании. Просто потому, что многопоточный тест «преуспевает» в тестировании не означает, что это правильный код.

Для конкретной ссылки JLS см. Раздел Happens Before (и остальная часть страницы).

Обратите внимание, что общее правило, если вы считаете, что у вас есть умный новый способ обойти «стандартные» нитевидные идиомы, вы, скорее всего, ошибаетесь.

+0

, если ничто в JSL не говорит о том, что поле должно быть объявлено изменчивым, откуда вы знаете, что оно должно быть объявлено как изменчивый? Ваше мнение о тестировании mutli-threaded кода более чем нормально, но вопрос был о JSL. – khachik

+0

@khachik - JLS дает конкретные сведения о том, что вы должны сделать, чтобы сделать код безопасным. в разделе, на который делается ссылка, есть множество вариантов. ничего в JLS _requires_ вы должны сделать ваш код потокобезопасным, поэтому вам не нужно делать поле volatile (иначе все поля будут по умолчанию нестабильными). – jtahlborn

+0

уверен. Это не отвечает на мой вопрос. – khachik

Смежные вопросы