java 原子变量_Java原子变量类需要注意的问题

在学习多线程时,遇到了原子变量类,它是基于 CAS 和 volatile 实现的,能够保障对共享变量进行 read-modify-write 更新操作的原子性和可见性。于是我就写了一段代码试试,自认为非常正确。

public class Test{

private static AtomicInteger ID = new AtomicInteger(0);

public static int nextID(){ //返回的ID范围为 1~100

if(ID.get() == 100) { //ID到达100时,则从1开始

ID.set(1);

return ID.get(); // return ID = 1;

}

else

return ID.incrementAndGet(); //++ID

}

public static void main(String[] args) throws Exception{

for(int i = 0; i < 5; i++){

new Thread(()->{

for(int j = 0; j < 100; j++)

nextID();

}).start();

}

Thread.sleep(1000); //应该输出100才对

System.out.println(ID);

}

}

用五个线程并发获得ID,每个线程获取100个,最后应该输出100才是,但试了好几次都不是100。原子变量类不是能保障原子性和可见性吗,为什么出现了竞态?

纠结了很久,还是很懵逼。后来发现 get 方法相当于读取一个 volatile 变量,而读取一个 volatile 变量时,不具备排他性!(AtomicInteger类内部使用了volatile修饰了value值,而volatile关键字不具备排他性)

也就是说,当一个线程刚读取到了共享的 volatile 变量的值时,其他线程可会马上对共享变量进行修改。如,线程A读取到ID的值为99时(还没对ID进行修改),其他线程可能马上就将ID加1了,此时共享变量为100了,其他线程再获取ID时,应该令ID=1才是,但线程A已经进入了else分支,它还认为ID=99,而不知道其他线程刚把ID加1变成了100,所以会吧ID加上1变成了101,这就出现了竞态。

《Java多线程编程实战指南 - 核心篇》中,作者说:“可见性的保障仅仅意味着一个线程能够读取到共享变量的相对新值,而不能保障该线程能读取到相应变量的最新值”。如volatile对可见性的保障就是保障的相对新值,由于volatile不具备排他性,所以有可能读线程刚读到一个相对新值,写线程就更改了共享变量,此时,读线程刚刚读取到的相对新值就不是最新的了。

作者对相对新值和最新值的定义:

对于同一个共享变量而言,一个线程更新了该变量的值之后,其他线程能够读取到这个更新后的值,那这个值就被称为该变量的 相对新值。

如果读取这个共享变量的线程在读取并使用该变量的时候其他线程无法更新该变量的值,那么该线程读取到的相对新值就被称为该变量的 最新值。需要加锁,才能读取到最新值。

解决办法,使用原子操作 compareAndSet:

private static int nextID(){ //返回的ID范围为 1~100

ID.compareAndSet(100, 0);

return ID.incrementAndGet();

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值