1700450359
1700450360
System.exit(0);
1700450361
1700450362
}
1700450363
1700450364
}
1700450365
1700450366
}
1700450367
1700450368
这是一段设计很巧妙的程序,要让volatile变量“出点丑”还是需要花点功夫的。此段程序的运行逻辑如下:
1700450369
1700450370
启动100个线程,修改共享资源count的值。
1700450371
1700450372
暂停15毫秒,观察活动线程数是否为1(即只剩下主线程在运行),若不为1,则再等待15毫秒。
1700450373
1700450374
判断共享资源是否是不安全的,即实际值与理想值是否相同,若不相同,则发现目标,此时count的值为脏数据。
1700450375
1700450376
如果没有找到,继续循环,直到达到最大循环次数为止。运行结果如下:
1700450377
1700450378
循环到第247遍,出现线程不安全情况
1700450379
1700450380
此时,count=999
1700450381
1700450382
这只是一种可能的结果,每次执行都有可能产生不同的结果。这也说明我们的count变量没有实现数据同步,在多个线程修改的情况下,count的实际值与理论值产生了偏差,直接说明了volatile关键字并不能保证线程安全。
1700450383
1700450384
在解释原因之前,我们先说一下自加操作。count++表示的是先取出count的值然后再加1,也就是count=count+1,所以,在某两个紧邻的时间片段内会发生如下神奇的事情:
1700450385
1700450386
(1)第一个时间片段
1700450387
1700450388
A线程获得执行机会,因为有关键字volatile修饰,所以它从主内存中获得count的最新值998,接下来的事情又分为两种类型:
1700450389
1700450390
如果是单CPU,此时调度器暂停A线程执行,出让执行机会给B线程,于是B线程也获得了count的最新值998。
1700450391
1700450392
如果是多CPU,此时线程A继续执行,而线程B也同时获得count的最新值998。
1700450393
1700450394
(2)第二个时间片段
1700450395
1700450396
如果是单CPU, B线程执行完加1动作(这是一个原子处理),count的值为999,由于是volatile类型的变量,所以直接写入主内存,然后A线程继续执行,计算的结果也是999,重新写入主内存中。
1700450397
1700450398
如果是多CPU, A线程执行完加1动作后修改主内存的变量count为999,线程B执行完毕后也修改主内存中的变量为999。
1700450399
1700450400
这两个时间片段执行完毕后,原本期望的结果为1000,但运行后的值却为999,这表示出现了线程不安全的情况。这也是我们要说明的:volatile关键字并不能保证线程安全,它只能保证当线程需要该变量的值时能够获得最新的值,而不能保证多个线程修改的安全性。
1700450401
1700450402
顺便说一下,在上面的代码中,UnsafeThread类的消耗CPU计算是必须的,其目的是加重线程的负荷,以便出现单个线程抢占整个CPU资源的情景,否则很难模拟出volatile线程不安全的情况,读者可以自行模拟测试。
1700450403
1700450404
注意 volatile不能保证数据是同步的,只能保证线程能够获得最新值。
1700450405
1700450406
1700450407
1700450408
[
上一页 ]
[ :1.700450359e+09 ]
[
下一页 ]