如何使 volatile count++ 操作成为线程安全的
how does the volatile count++ operation be made thread safe
我一直在浏览 JCIP,作者在那里说..
A special case of thread confinement applies to volatile variables. It is safe to perform read-modify-write operations on shared volatile variables as long as you ensure that the volatile variable is only written from a single thread
例如 count++ 被认为是一个复合操作(读取值、加一和更新值)并且将 count 声明为 volatile 不会使该操作成为原子操作,因此这里不保证线程安全!!我对吗 ??但是作者在这里说,如果我们确保volatile变量只从一个线程写入,我们就可以修复它。
我不明白这一点。请提供插图。
count++本质上是两个操作,读取count的值和store count + 1。
如果两个线程同时尝试执行 count++,它们可能都读取 count 的旧值并将其递增 1。因此,最后,如果您不添加任何同步机制,您最终的最终值将是 count + 1,而不是 count + 2。
Volatile 不保证这种场景下的线程安全(不是原子的),它只保证最近存储的值对所有线程可用。但这在上述场景中并没有多大帮助。您需要确保在存储新值之前,没有其他线程会读取计数的旧值。
如果您需要线程安全的计数器,我建议您查看 AtomicInteger(和类似的)类。他们提供了 count++ 的原子版本; incrementAndGet 方法。
典型的用例可能如下所示:
public class Foo implements Runnable {
private volatile int itemsProcessed;
public int getItemsProcessed() { return itemsProcessed; }
@Override
public void run() {
while (true) { //this is just an example so we don't care about stopping
processItem(); //process a single item
itemsProcessed++;
}
}
}
现在其他人无需额外同步即可查询您线程的进度,但只允许线程本身更新它。对于其他人,该字段是只读的。
如果没有 volatile
,其他线程可能根本看不到 itemProcessed
的任何变化,甚至可能会以古怪的顺序看到它们,有时增加,有时减少。
唯一可以让多个线程写入一个 volatile
变量而无需任何额外同步的情况是写入 idempotent(即,多个写入具有与单个效果相同)。这可以在用于停止线程的模式中看到:
public class Foo implements Runnable {
private volatile boolean stopped = false;
public void stopProcessing() {
stopped = true;
}
public int getItemsProcessed() { return itemsProcessed; }
@Override
public void run() {
while (!stopped) {
processItem(); //process a single item
}
}
}
这里其他线程唯一能做的就是将 stopped
设置为 true。不管有多少人做,以什么顺序做,结果总是一样的。
这背后的想法是,为了避免数据竞争,必须只有一个写入线程,然后可见性保证由 Java 内存模型通过 happens-before- 处理关系。如果有多个写入线程,则无法通过将变量声明为 "volatile"
来使隐式复合操作成为原子操作
17.4.4. Synchronization Order
A write to a volatile variable v (§8.3.1.4) synchronizes-with all
subsequent reads of v by any thread (where "subsequent" is defined
according to the synchronization order).
我猜你和我有类似的疑虑here。虽然只有一个线程对 volatile 变量执行读取-修改-写入操作,但读取线程确实可以读取变量的 "temporary" 值;但这并不意味着该程序不是线程安全的。它读取 "temporary" 值,因为写入线程尚未将其写入内存。换句话说,写入线程和读取线程是 "seeing the same state of the variable" -- 它们仍然完全同步。
我一直在浏览 JCIP,作者在那里说..
A special case of thread confinement applies to volatile variables. It is safe to perform read-modify-write operations on shared volatile variables as long as you ensure that the volatile variable is only written from a single thread
例如 count++ 被认为是一个复合操作(读取值、加一和更新值)并且将 count 声明为 volatile 不会使该操作成为原子操作,因此这里不保证线程安全!!我对吗 ??但是作者在这里说,如果我们确保volatile变量只从一个线程写入,我们就可以修复它。 我不明白这一点。请提供插图。
count++本质上是两个操作,读取count的值和store count + 1。
如果两个线程同时尝试执行 count++,它们可能都读取 count 的旧值并将其递增 1。因此,最后,如果您不添加任何同步机制,您最终的最终值将是 count + 1,而不是 count + 2。
Volatile 不保证这种场景下的线程安全(不是原子的),它只保证最近存储的值对所有线程可用。但这在上述场景中并没有多大帮助。您需要确保在存储新值之前,没有其他线程会读取计数的旧值。
如果您需要线程安全的计数器,我建议您查看 AtomicInteger(和类似的)类。他们提供了 count++ 的原子版本; incrementAndGet 方法。
典型的用例可能如下所示:
public class Foo implements Runnable {
private volatile int itemsProcessed;
public int getItemsProcessed() { return itemsProcessed; }
@Override
public void run() {
while (true) { //this is just an example so we don't care about stopping
processItem(); //process a single item
itemsProcessed++;
}
}
}
现在其他人无需额外同步即可查询您线程的进度,但只允许线程本身更新它。对于其他人,该字段是只读的。
如果没有 volatile
,其他线程可能根本看不到 itemProcessed
的任何变化,甚至可能会以古怪的顺序看到它们,有时增加,有时减少。
唯一可以让多个线程写入一个 volatile
变量而无需任何额外同步的情况是写入 idempotent(即,多个写入具有与单个效果相同)。这可以在用于停止线程的模式中看到:
public class Foo implements Runnable {
private volatile boolean stopped = false;
public void stopProcessing() {
stopped = true;
}
public int getItemsProcessed() { return itemsProcessed; }
@Override
public void run() {
while (!stopped) {
processItem(); //process a single item
}
}
}
这里其他线程唯一能做的就是将 stopped
设置为 true。不管有多少人做,以什么顺序做,结果总是一样的。
这背后的想法是,为了避免数据竞争,必须只有一个写入线程,然后可见性保证由 Java 内存模型通过 happens-before- 处理关系。如果有多个写入线程,则无法通过将变量声明为 "volatile"
来使隐式复合操作成为原子操作17.4.4. Synchronization Order
A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).
我猜你和我有类似的疑虑here。虽然只有一个线程对 volatile 变量执行读取-修改-写入操作,但读取线程确实可以读取变量的 "temporary" 值;但这并不意味着该程序不是线程安全的。它读取 "temporary" 值,因为写入线程尚未将其写入内存。换句话说,写入线程和读取线程是 "seeing the same state of the variable" -- 它们仍然完全同步。