持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
原子性
- 将自增代码 编译 javac Demo.java 成字节码二进制文件后查看
- javap 查看内容 javap -v Demo.class > Demo.txt
原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分(不可中断性)。
将整个操作视作一个整体,资源在该次操作中保持一致,这是原子性的核心特征。
举个例子,开启6个线程,每个线程内自增到1w ,6个线程共享变量sum ,这就使得最后sum的值未知 ,整体是小于等于6w的 ,如果加锁的话,输出结果是6w 。
//控制台输出结果
thread is running ....
thread is running ....
thread is running ....
thread is running ....
thread is running ....
thread is running ....
60000
CAS(Compare and Swap)
Compare and swap 比较和交换。属于硬件同步原语,处理器提供了基本内存操作的原子性保证。
CAS操作需要输入两个数值,一个就值A(期望操作前的值)和一个新值B ,在操作期间先对旧值进行比较,若没有发生变化,才交换成新值,发生了变化则不交换。
JAVA中的sun.misc.Unsafe类,提供了compareAndSwapInt() 和 compareAndSwapLong()等几个方法实现CAS 。
举了个 循环的例子,说明同一个时间片的安全性问题 。
Java底层的Unsafe实现cas
//客户端运行结果
thread is running ....
thread is running ....
thread is running ....
thread is running ....
thread is running ....
thread is running ....
60000
提出了 lock 和 synchronization 方式浪费资源,cas 硬件方式,又引出了自旋---性能损耗 ---引起cpu占用 。
J.U.C包内的原子操作封装类
AtomicBoolean:原子更新布尔类型
Atomiclnteger:原子更新整型
AtomicLong:原子更新长整型
AtomiclntegerArray:原子更新整型数组里的元素
AtomicLongArray:原子更新长整型数组里的元素
AtomicReferenceArray:原子更新引用类型数组里的元素
AtomiclntegerFieldUpdater:原子更新整型的字段的更新器
AtomicLongFieldUpdater:原子更新长整型字段的更新器
AtomicReferenceFieldUpdater:原子更新引用类型里的字段
AtomicReference:原子更新引用类型
AtomicStampedReference:原子更新带有版本号的引用类型
AtomicMarkableReference:原子更新带有标记位的引用类型
CAS的三个问题
- 循环+CAS,自旋的实现让所有线程都处于高频运行,争抢CPU执行时间的状态。如果操作长时间不成功,会带来很大的CPU资源消耗。
- 仅针对单个变量的操作,不能用于多个变量来实现原子操作。
- ABA问题。
ABA问题
thread1、thread2同时读取到i=0 后,
thread1、thread2都要执行CAS(0,1)操作,
假设thread1、thread2操作稍之后与thread1,则thread1执行成功,
thread1紧接着执行了CAS(1,0),将i的值改回0 ,
线程2本应该失败的CAS(0,1)操作,此时确成功了。
线程安全的概念
public class Demo{
public int i = 0 ;
public void incr(){
i++;
}
}
竞态条件:如果程序运行顺序的改变会影响最终结果,就说存在竞态条件。大多数竞态条件的本质,就是基于某种可能失效的观察结果来做出判断或执行某个计算。
临界区:存在静态条件的代码区域叫临界区。
共享资源
只有当多个线程更新共享资源时,才会发生竞态条件,可能会出现线程安全问题。
栈封闭时,不会在线程之间共享的变量,都是线程安全的。
局部对象引用本身不共享,但是引用的对象存储在共享堆中 。如果方法内创建的对象,只是在方法中传递,并且不对其他线程可用,那么也是线程安全的。
不可变的共享对象来保证对象在线程间共享时,不会被修改,从而实现线程安全。
实例被创建,value变量就不能再被修改,这就是不可变形。
使用Threadlocal时,相当于不同的线程操作的是不同的资源,所以不存在线程安全问题。
今日重点
- i++ 这个操作不是原子操作
- 原子操作的概念
- CAS机制的概念,利用CAS实现原子性的数字变更
- AtomicInteger等类底层就是利用CAS机制实现
- JDK提出了高并发场景性能更好的累加计数器
- 带有版本号的数字引用类型,可以实现版本号锁
- 线程安全相关的概念