打字猴:1.700450359e+09
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 ]  [ 下一页 ]