2013-02-14 3 views
1

Я часто использую следующий шаблон, чтобы создать Cancellable тему:Нужно ли AtomicBoolean создать отменную нить?

public class CounterLoop implements Runnable { 

    private volatile AtomicBoolean cancelPending = new AtomicBoolean(false); 

    @Override 
    public void run() { 
     while (!cancelPending.get()) { 
      //count 
     } 
    } 

    public void cancel() { 
     cancelPending.set(true); 
    } 
} 

Но я не уверен, что cancelPending ДОЛЖЕН быть AtomicBoolean. Можем ли мы просто использовать нормальное булево значение в этом случае?

+1

правило большого пальца: если вы не используете CAS, вам не нужен AtomicXXX. 2-е правило: если вам нужно протестировать/изменить (и изменить можно перейти в несколько состояний), вам нужен CAS. Следовательно, в вашем случае вам не нужен AtomicXXX, но вместо этого вы можете использовать Thread.interrupt. 'interrupt()' также больше подходит (может отменить блокировку и может отменить IO). В вашем случае, если вы идете w/AtomicXXX, вы должны сделать его окончательным, а не изменчивым. – bestsss

ответ

2

Используя оба volatile и AtomicBoolean ненужно. Если вы объявляете переменную cancelPending в final следующим образом:

private final AtomicBoolean cancelPending = new AtomicBoolean(false); 

семантика JLS для final полеев означает, что синхронизация (или volatile) не будет необходимо. Все потоки будут видеть правильное значение для ссылки cancelPending.JLS 17.5 состояния:

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

... но для нормальных полей таких гарантий нет; то есть не final, а не volatile.

Вы также можете просто объявить cancelPending как volatile boolean ... так как вы, кажется, не используете тестовую установку AtomicBoolean.

Однако, если вы использовали энергонезависимую boolean вам нужно будет использовать synchronized, чтобы гарантировать, что все потоки увидеть копию уточненного в cancelPending флага.

0

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

+0

Нет, я отредактировал свой ответ. – gkuzmin

+0

На самом деле вам нужно либо «volatile», либо «final» ... из-за того, как работает модель памяти. –

+0

Я думаю, что Hiep не хочет изменять значение 'cancelPending', поэтому ключевое слово' final' не требуется, но будет более точным. – gkuzmin

2

Вы можете использовать volatile boolean вместо проблем.

Обратите внимание, что это применимо только в случаях, подобных этому, когда логическое значение изменяется только до определенного значения (true в этом случае). Если логическое значение может быть изменено либо на true, либо на false в любое время, то вам может понадобиться AtomicBoolean, чтобы обнаружить и принять участие в гонке.

Однако - образец, который вы описываете, имеет врожденный запах. Зацикливая на логическом (volatile или нет), вы, вероятно, обнаружите, что пытаетесь вставить какой-то механизм sleep или прервать поток.

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

0

Да, вы можете. Вы можете использовать нелетучий AtomicBoolean (полагаясь на встроенную безопасность потоков) или использовать любую другую изменчивую переменную.

В соответствии с моделью памяти Java (JMM) оба варианта приводят к правильно синхронизированной программе, где чтение и запись переменной cancelPending не может привести к гонке данных.

+0

На самом деле вам нужно либо «volatile», либо «final»; см. ссылку JLS в ответе. –

+0

@StephenC: Вы правы - в общем случае требуется «final», когда переменная не объявлена ​​изменчивой. Однако на практике обычно поток, который прерывает, является тем же самым потоком, где флаг инициализируется. В этом конкретном сценарии между сообщениями и чтениями гарантируется отношение «произойдет до». –

0

Использование изменчивой булевой переменной в этом контексте является безопасным, хотя некоторые из них могут считать это плохой практикой. Проконсультируйтесь с this thread, чтобы узнать, почему.

Ваше решение использовать переменную Atomic * представляется лучшим вариантом, даже если синхронизация может привести к излишним накладным расходам по сравнению с изменчивой переменной.

Вы также можете использовать критическую секцию

Object lock = new Object(); 

@Override 
public void run() { 
    synchronized (lock) { 
    if (cancelPending) { 
     return; 
    } 
    } 
} 

или синхронизированный метод.

synchronized public boolean shouldStop() { 
    return shouldStop; 
} 

synchronized public void setStop(boolean stop) { 
    shouldStop = stop; 
} 
+0

@OldCurmudgeon Я не согласен. Это будет работать. Синхронизированный раздел обеспечивает обновление памяти. – Dariusz

+1

Я стою исправленный - извиняюсь за свою поспешность и снимаю большую часть моего комментария (и нисходящего). Тем не менее, я по-прежнему считаю, что использование 'synchronized' для сброса кеша слишком велико, когда доступно' volatile'. – OldCurmudgeon

+0

@StephenC Первое предложение моего ответа означает, что «использование нормального булева - это решение, которое может иногда работать, но иногда может и не быть», которое, в свою очередь, должно интерпретироваться как «не использовать его». Я отредактирую его, так как этот SE не место для таких замечаний. – Dariusz

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