打字猴:1.700450379e+09
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
1700450409 编写高质量代码:改善Java程序的151个建议 [:1700438199]
1700450410 编写高质量代码:改善Java程序的151个建议 建议124:异步运算考虑使用Callable接口
1700450411
1700450412 多线程应用有两种实现方式,一种是实现Runnable接口,另一种是继承Thread类,这两个方式都有缺点:run方法没有返回值,不能抛出异常(这两个缺点归根到底是Runable接口的缺陷,Thread也是实现了Runnable接口),如果需要知道一个线程的运行结果就需要用户自行设计,线程类自身也不能提供返回值和异常。但是从Java 1.5开始引入了一个新的接口Callable,它类似于Runable接口,实现它就可以实现多线程任务,Callable的接口定义如下:
1700450413
1700450414 public interface Callable<V>{
1700450415
1700450416 //具有返回值,并可抛出异常
1700450417
1700450418 V call()throws Exception;
1700450419
1700450420 }
1700450421
1700450422 实现Callable接口的类,只是表明它是一个可调用的任务,并不表示它具有多线程运算能力,还是需要执行器来执行的。我们先编写一个任务类,代码如下:
1700450423
1700450424 //税款计算器
1700450425
1700450426 class TaxCalculator implements Callable<Integer>{
1700450427
1700450428 //本金
[ 上一页 ]  [ :1.700450379e+09 ]  [ 下一页 ]