在学习多线程时,遇到了原子变量类,它是基于 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();
}

  

Java原子变量类需要注意的问题的更多相关文章

  1. 全面了解 Java 原子变量类

    目录   一.原子变量类简介  二.基本类型  三.引用类型  四.数组类型  五.属性更新器类型  参考资料

  2. Java锁与非阻塞算法的性能比较与分析+原子变量类的应用

    15.原子变量与非阻塞同步机制 在java.util.concurrent包中的许多类,比如Semaphore和ConcurrentLinkedQueue,都提供了比使用Synchronized更好的 ...

  3. Java原子变量

    实现全局自增id最简单有效的方式是什么?java.util.concurrent.atomic包定义了一些常见类型的原子变量.这些原子变量为我们提供了一种操作单一变量无锁(lock-free)的线程安 ...

  4. 谈论Java原子变量和同步的效率 -- 颠覆你的生活

    我们认为,由于思维定式原子变量总是比同步运行的速度更快,我想是这样也已经,直到实现了ID在第一次测试过程生成器不具有在这样一个迷迷糊糊的东西. 测试代码: import java.util.Array ...

  5. 【Java】变量类接口_学习笔记

    变量.类和接口 1.变量的类型 实例变量(不以static修饰) 成员变量 类变量(以static修饰) 所有变量 形参(方法签名中定义的变量) 局部变量         方法局部变量(在方法内定义) ...

  6. 用Java原子变量的CAS方法实现一个自旋锁

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/5999610. ...

  7. 《Java并发编程实战》第十五章 原子变量与非堵塞同步机制 读书笔记

    一.锁的劣势 锁定后假设未释放.再次请求锁时会造成堵塞.多线程调度通常遇到堵塞会进行上下文切换,造成很多其它的开销. 在挂起与恢复线程等过程中存在着非常大的开销,而且通常存在着较长时间的中断. 锁可能 ...

  8. java并发编程实战:第十五章----原子变量与非阻塞机制

    非阻塞算法:使用底层的原子机器指令(例如比较并交换指令)代替锁来确保数据在并发访问中的一致性 应用于在操作系统和JVM中实现线程 / 进程调度机制.垃圾回收机制以及锁和其他并发数据结构 可伸缩性和活跃 ...

  9. java并发编程(8)原子变量和非阻塞的同步机制

    原子变量和非阻塞的同步机制 一.锁的劣势 1.在多线程下:锁的挂起和恢复等过程存在着很大的开销(及时现代的jvm会判断何时使用挂起,何时自旋等待) 2.volatile:轻量级别的同步机制,但是不能用 ...

随机推荐

  1. 对 Redux 一头雾水?看完这篇就懂了

    首先,学习 Redux 可能会很困难 当你终于学会了如何使用 React,也有了自己去构建一些应用的信心,那会是一种非常棒的感觉.你学会了管理状态,一切看起来井井有条.但是,很有可能这就到了你该学习 ...

  2. 关于knockout下拉多选值的应用

    在最近的开发过程中,应用了一些关于knockout的下拉项目. 关于下拉多选的开发在这里做一个记录. 下面直接上代码 添加的时候,无需给初始值 --viewmodel function ViewMod ...

  3. 推荐中的多任务学习-YouTube视频推荐

    本文将介绍Google发表在RecSys'19 的论文<Recommending What Video to Watch Next: A Multitask Ranking System> ...

  4. C#录制视频

    这是一个使用C#语言制作的录制框架,支持录制桌面,多屏,声音,摄像头,某个应用程序的界面 1.安装 使用此框架需要安装扩展包Kogel.Record,可以Nuget上搜索 或者使用Nuget命令 In ...

  5. Java网络编程系列之TCP连接状态

    1.TCP连接状态 LISTEN:Server端打开一个socket进行监听,状态置为LISTEN SYN_SENT:Client端发送SYN请求给Server端,状态由CLOSED变为SYN_SEN ...

  6. AQS原理及应用

    To use this class as the basis of a synchronizer, redefine the * following methods, as applicable, b ...

  7. POJ 1269 Intersecting Lines(判断两直线位置关系)

    题目传送门:POJ 1269 Intersecting Lines Description We all know that a pair of distinct points on a plane ...

  8. DevExpress 控件用法笔记(VB)

    1.ChartControl 显示条形图 ChartControl1.Titles.Clear() ChartControl1.Series.Clear() Dim db As DataTable S ...

  9. YOLO V3训练自己的数据集

    数据的输入几乎和Faster rcnn一样,标签格式xml是一样的. 相比Faster rcnn,数据多了一步处理,通过voc_annotation.py将图片路径和bbox+class存储在txt下 ...

  10. python列表的 + 、* 、in 、 not in 、 len() 、 max() 、 min()

    + 列表拼接 first_list = [1,2,3] + ['a',5] # + 将列表拼接 print(first_list) # [1, 2, 3, 'a', 5] *  列表与数字n相乘 : ...