我正在学习 Java 中的并发性,并且正在观看 YouTube 视频。这是关于多线程环境下增量操作的解释。解决方案可能是一个易失性变量或一个同步块。但我的问题是,如果我使用同步块,我是否还需要使用易失性变量。如果我有一个同步块还不够吗?我看到讲师使方法同步,并且使变量可变。在我看来,使方法同步就足够了,因为当线程进入同步方法时,它会从主内存中获取变量的值,而当线程退出同步方法时,它会将值直接发送到主内存,并且该值可用于其他线程。所以我认为不必强制使变量为易失性。
这是代码:
public class ClientTest {
public static void main(String[] args) {
ExecutorService executorService = null;
Counter counter = new Counter();
try {
executorService = Executors.newFixedThreadPool(2);
Runnable task1 = () -> {
for (int i = 1; i <= 20000; i++) {
counter.increment();
}
};
Runnable task2 = () -> {
for (int i = 1; i <= 80000; i++) {
counter.increment();
}
};
executorService.submit(task1);
executorService.submit(task2);
executorService.awaitTermination(1, TimeUnit.SECONDS);
System.out.println("Final counter value: " + counter.getCounter());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public class Counter {
private int counter;
public int getCounter() {
return counter;
}
public synchronized void increment() {
counter++;
}
}
计数器变量必须声明为 volatile 吗?
为了
Counter
类的正确行为,您需要标记 counter
字段 volatile
,或者还需要创建 getCounter()
方法 synchronized
。
在代码的具体示例中,它可能会正常工作,但这同样可能是因为您在执行器服务上调用
awaitTermination
,并且提交的任务可能会在超时到期之前完成。我认为,但不确定,这将在执行器线程上执行的任务之间建立发生之前的关系,因此您的主线程将保证看到counter
的最新值。
但是,如果没有这种先于关系,主线程(或任何其他线程)不能保证看到
counter
的最新值。要建立这样的发生之前,您需要标记字段 volatile
,或在方法上使用 synchronized
。
顺便说一句,在执行器服务上调用
awaitTermination
而不调用 shutdown()
是不正确的。