![](/img/trans.png)
[英]What's the risk if I replace AtomicBoolean with volatile in this code?
[英]Controlling an instance's state with AtomicBoolean
我需要確保特定的啟動和停止代碼在每個實例生命周期內僅執行一次,並且該實例不能“重新啟動”。 以下代碼是否適合於多個線程可能作用於該實例的情況?
public final class MyRunnable {
private final AtomicBoolean active = new AtomicBoolean(false);
private final AtomicBoolean closed = new AtomicBoolean(false);
public void start() {
if (closed.get()) {
throw new IllegalStateException("Already closed!");
}
if (active.get()) {
throw new IllegalStateException("Already running!");
}
active.set(true);
// My one-time start code.
// My runnable code.
}
public void stop() {
if (closed.get()) {
throw new IllegalStateException("Already stopped!");
}
if (!active.get()) {
throw new IllegalStateException("Stopping or already stopped!");
}
active.set(false);
// My one-time stop code.
closed.set(true);
}
}
由於兩個原因,我會選擇一個單一的三值狀態。
首先,在active,closed
“元組”的4個可能值中active,closed
只有3個有意義,將兩者都設置為true
導致(可能是良性,但仍然是)無效狀態。 您可以將其視為純裝飾,但是清晰的設計通常會帶來其他好處。
這使我們巧妙地得出了第二個更可怕的原因:
active.set(false);
// <-- what if someone calls start() here?
closed.set(true); //I assume you wanted to set it to true
正如你可以從我的評論看,你已經有了一個薄弱環節在那里,有人可以想見,調用start()
你設置后active
到false
,但之前設置的closed
至true
。
現在您可能只是說“好吧,讓我們先將它們交換掉,然后將其設置為closed
”,但是接下來您必須解釋為什么JVM絕對不會對二者進行重新排序。 最后,您可能會將兩個標志都設置為true
,從而導致上述“無效狀態”。
這里還有另一個單獨的問題:您遵循的模式是調用get()
來檢查值,然后set()
為其他值。 正如PetrosP指出的那樣 ,這不是原子操作,您可以調用start()
1000次,並且所有這些都將active
視為false
。 您需要改為使用compareAndSet
,它是原子的(這是Atomic*
類的全部要點),因此可以保證只有一個線程可以提升狀態標志。
因此,讓我們使用一個3值狀態(為了簡單起見,我使用AtomicInteger
,但可以使用AtomicReference
和一個真正的enum
)將兩者結合起來,然后執行compareAndSet()
:
public final class MyRunnable {
private static final int READY_TO_START = 0;
private static final int ACTIVE = 1;
private static final int STOPPED = 2;
private final AtomicInteger status = new AtomicInteger(READY_TO_START);
public void start() {
if (!status.compareAndSet(READY_TO_START, ACTIVE)) {
throw new IllegalStateException("Already started");
}
// My one-time start code.
}
public void stop() {
if (!status.compareAndSet(ACTIVE, STOPPED)) {
throw new IllegalStateException("Can't stop, either not started or already stopped");
}
// My one-time stop code.
}
}
該解決方案是不夠的。 考慮這種情況:兩個線程同時進入start()。 一個調用active.get()
並返回false
。 然后第二個調用active.get()
,它也active.get()
false
。 在這種情況下,它們都將繼續。 然后第一個將active設置為true。 此時的第二個代碼也將active設置為true,並且它們都將繼續執行應運行一次的其余代碼。
一個解決方案可能是這樣的:
public final class MyRunnable {
private final AtomicBoolean active = new AtomicBoolean(false);
private final AtomicBoolean closed = new AtomicBoolean(false);
public void start() {
synchronized (this) {
if (closed.get()) {
throw new IllegalStateException("Already closed!");
}
if (active.get()) {
throw new IllegalStateException("Already running!");
}
active.set(true);
}
// My one-time start code.
// My runnable code.
}
public void stop() {
synchronized (this) {
if (closed.get()) {
throw new IllegalStateException("Already stopped!");
}
if (!active.get()) {
throw new IllegalStateException("Stopping or already stopped!");
}
// My one-time stop code.
closed.set(false);
active.set(false);
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.