java多线程中的线程安全
我找到了关于线程安全性的代码,但没有给出示例的人的任何解释。我想理解为什么如果我不在“count”之前设置“synchronized”变量,那么计数值将是非原子的(始终=200是期望的结果)。谢谢
public class Example {
private static int count = 0;
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
//add synchronized
synchronized (Example.class){
count++;
}
}
}).start();
}
try{
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(count);
}
}
# 1 楼答案
++
不是原子的count++
操作不是原子操作。这意味着这不是一次单独的行动。{首先,变量中存储的值被加载(复制)到CPU核心的寄存器中
第二,核心寄存器中的值是递增的
第三也是最后一点,新的递增值从核心寄存器写入(复制)到内存中变量的内容。然后,内核的寄存器可以自由地为其他工作分配其他值
两个或多个线程完全有可能读取变量的相同值,例如
42
。然后,这些线程中的每一个都将继续将该值增加到相同的新值43
。然后,它们将各自向同一个变量写回43,无意中反复存储43
添加
synchronized
可以消除这种竞争条件。当第一个线程获得锁时,第二个和第三个线程必须等待。因此,第一个线程保证能够单独读取、递增和写入新值,从42到43。完成后,该方法退出,从而释放锁。争夺锁的第二个线程获得了许可,获得了锁,并且能够在不受干扰的情况下读取、递增和写入新值44。等等,线程安全另一个问题是可见性
然而,这个代码仍然被破坏
这段代码存在可见性问题,不同的线程可能会读取缓存中保存的过时值。但这是另一个话题。搜索以了解有关
volatile
关键字、AtomicInteger
类和Java内存模型的更多信息# 2 楼答案
简单的回答是:因为JLS这么说
如果不使用
synchronized
(或volatile
或类似的东西),那么Java语言规范(JLS)不能保证主线程将看到子线程写入count
的值这在JLS的Java内存模型部分有详细说明。但是规格非常技术化
简化的版本是,如果在连接写入和读取的(HB)关系之前没有发生,则不能保证读取变量时看到之前写入的值。然后有一系列规则说明何时存在HB关系。其中一条规则是,释放互斥锁的线程与获取互斥锁的不同线程之间存在HB
另一种直观(但不完整且技术上不准确)的解释是
count
的最新值可能会缓存在寄存器或芯片组的内存缓存中。synchronized
构造将值刷新为内存这是一个不准确的解释,因为JLS没有提到任何关于寄存器、缓存等的内容。相反,内存可见性保证了JLS指定的通常由Java编译器实现,该编译器插入指令,将寄存器写入内存、刷新缓存、或硬件平台所需的任何内容
另一点需要注意的是,这并不是关于
count++
是原子还是不是1。它是关于对count
的更改的结果是否对其他线程可见1-它不是原子的,但是对于原子操作,你会得到与简单赋值相同的效果
# 3 楼答案
让我们通过一个华尔街的例子回到基本面
比方说,你(打T1)和你的朋友(打T2)决定在华尔街的一家咖啡馆见面。你们俩是同时开始的,比如说从华尔街的南端开始(尽管你们不是一起走的)。你们在人行道的一边醒来,你们的朋友在华尔街人行道的另一边走,你们都往北走(方向相同)
现在,假设你来到一家咖啡馆前,你以为这就是你和你朋友决定见面的咖啡馆,所以你走进咖啡馆,点了一杯冷咖啡,开始一边等一边啜饮
但是,在路的另一边,类似的事情发生了,你的朋友遇到一家咖啡店,点了一块热巧克力,正在等你
过了一会儿,你们都决定另一个人不来了,放弃了会面的计划
你们都错过了目的地和时间。为什么会这样?不用说,但是,因为你没有决定确切的地点
密码
解决你和你朋友刚刚遇到的问题
从技术上讲,操作计数器++实际上分三步进行
如果两个线程同时处理计数器变量,计数器的最终值将是不确定的。例如,Thread1可以将计数器的值读取为1,同时thread2可以将变量的值读取为1。两个线程最后都将计数器的值增加到2。这被称为竞赛条件
为了避免这个问题,操作计数器++必须是原子的。要使其原子化,需要同步线程的执行。每个线程都应该以有组织的方式修改计数器
我建议您在实践中阅读Java并发这本书,每个开发人员都应该阅读这本书