1、原子性概念
原子性是指一个操作是不可中断的,要么全部执行成功,要么全部执行失败,有着“同生共死”的感觉。即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其它的线程干扰。
例如语句(a++)实际上包含了三个操作:
- 读取变量a的值
- 对a进行加1的操作
- 将计算后的值再赋值给变量a
像这三个操作就无法构成原子性操作。
2、原子类的工作原理-CAS机制
2.1 原子类概述
在java.util.concurrent.atomic包下定义了一些对“变量操作”的“原子类”,例:
- java.util.concurrent.atomic.AtomicInteger:对int变量操作的“原子类”;
- java.util.concurrent.atomic.AtomicLong:对long变量操作的“原子类”;
- java.util.concurrent.atomic.AtomicBoolean:对boolean变量操作的“原子类”;
像这些原子类都可以保证对变量操作的:原子性、有序性、可见性。
2.2 AtomicInteger类示例
-
我们可以通过AtomicInteger类,来看看原子类是如何使用的:
1.线程类:
public class MyThread extends Thread { public static volatile int a = 0; @Override public void run() { for (int i = 0; i < 10000; i++) { //线程1:取出a的值a=0(被暂停) a++; //写回 } System.out.println("修改完毕!"); } }
2.测试类:
public class Demo { public static void main(String[] args) throws InterruptedException { //1.启动两个线程 MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); t2.start(); Thread.sleep(1000); System.out.println("获取a最终值:" + MyThread.a.get()); } }
使用原子类对变量操作,无论程序运行多少次,其结果都是正确的!
2.3 原子类的工作原理
-
先来看一下调用过程
-
在Unsafe类中,调用了一个:compareAndSwapInt()方法,此方法的几个参数:
var1:传入的AtomicInteger对象
var2:AtommicInteger内部变量的偏移地址
var5:之前取出的AtomicInteger中的值;
var5 + var4:预期结果此方法使用了一种"比较并交换(Compare And Swap)"的机制,它会用var1和var2先获取内存AtomicInteger的值,然后和传入的,之前获取的值var5做一下比较,也就是比较当前内存的值和预期的值是否一致,如果一致就修改为var5 + var4,否则就继续循环,再次获取AtomicInteger中的值,再进行比较并交换,直至成功交换为止。
compareAndSwapInt()方法是"线程安全"的。 -
我们假设两个线程交替运行的情况,看看它是怎样工作的:
- 初始AtomicInteger的值为0
- 线程A执行:var5 = this.getIntVolatile(var1,var2);获取的结果为:0
- 线程A被暂停
- 线程B执行:var5 = this.getIntVolatile(var1,var2);获取的结果为:0
- 线程B执行:this.compareAndSwapInt(var1,var2,var5,var5 + var4)
- 线程B成功将AtomicInteger中的值改为1
- 线程A恢复运行,执行:this.compareAndSwapInt(var1,var2,var5,var5 + var4)
- 此时线程A使用var1和var2从AtomicInteger中获取的值为:1,而传入的var5为0,比较失败,返回false,继续循环。
- 线程A执行:var5 = this.getIntVolatile(var1,var2);获取的结果为:1
- 线程A执行:this.compareAndSwapInt(var1,var2,var5,var5 + var4)
- 此时线程A使用var1和var2从AtomicInteger中获取的值为:1,而传入的var5为1,比较成功,将其修改为var5 + var4,也就是2,将AtomicInteger中的值改为2,结束。
CAS机制也被称为:乐观锁。因为大部分比较的结果为true,就直接修改了。只有少部分多线程并发的情况会导致CAS失败,而再次循环。
2.4 如何描述原子类的执行流程及原理?
说到原子类,Java中提供了一些基本数据类型对应的原子类和一些引用数据类型对应的原子类,我们在平常使用变量或者一些引用数据类型的时候,如果考虑到多线程场景,一定要想到原子性问题,这里我就简单用经常使用的AtomicInteger举个例子说明,假如有多个线程在同时访问一个共享变量的时候,为了保证原子性,我们会采用AtomicInteger原子类来操作变量,如果要让变量实现自增操作,那我们就使用AtomicInteger提供的内部方法getAndIncrement()来进行自增!
- 此时假如A线程在操作共享变量,同时B线程也去操作变量,都会执行AtomicInteger提供的自增方法getAndIncrement() ,再调用Unsafe中的getAndAddInt(this,valueOffset,1) 。
- 参数 this代表当前对象( AtomicInteger类的对象)
- valueOffset代表AtomicInteger内部变量的偏移地址
- 1代表自增量
- 接着执行getAndAddInt() ,这个方法中是能够解决原子性问题的关键所在,这个方法的主体结构是由 一个变量 var5 和一个 do…while 循环体组成。
- 通过代码分析,首先do当中的代码块一定会执行(根据参数this,valueOffset)获取一下共享变量的当前值,这个动作就相当于拍一个快照做一下记录。接下来就执行while条件判断 (仔细观察发现while条件中调用了一个叫做compareAndSwapInt(有四个参数) ,这个方法的体现的本质是把共享内存的变量值再获取一遍,然后和刚刚记录的快照进行对比,如果对比一样,那就完成自增1返回true,如果不一样返回false,继续执行do中的代码,就是把共享变量的最新的值再记录一次快照,以备下一次比较使用), 以此类推这种机制叫做CAS机制,即比较后,通过比较的结果来决定是否对共享变量进行修改!