1.引子
今天这篇文章,我们暂时结束了锁小节内容,进入原子性小节,内容稍微有点偏底层,不过你不用担心,相对还是比较好理解的。
我们一直在说线程安全,那么线程安全有三个基本要素,你知道是哪三个吗?它们分别是:
- 可见性
- 原子性
- 有序性
可见性是指内存可见性,java内存模型你应该要熟系,如果你不熟悉的话,先看一下下面这个图:
如图所示,在java内存模型中,每个线程首先都有自己的私人空间,称为:工作内存。所有线程共享公共空间,称为:主内存。
那么具体可见性,是指什么呢?指的是当一个线程修改某个变量值后,如果要保障可见性(其它线程可以看见的话),需要将变量值同步到主内存中。同理一个线程操作某个变量,如果要保障可见性的话,需要首先从主内存同步变量到自己的工作内存中。这就是线程安全第一要素:可见性。
我们接着看线程安全第二要素:原子性。所谓原子性,即是一个整体不可分割,我们前面掌握的锁,即是在应用层保障原子性的手段。今天我们要学习的是偏底层的原子性,是由cpu指令层面的原子性。
最后再来看线程安全的第三要素:有序性。这里的有序性比较好理解,指的是线程内有序,线程之间还是无序的。
好了,解释清楚线程安全三要素:可见性、原子性、有序性。我们可以介绍cas了,那么cas到底是什么呢?cas你可以理解为是一种思想,它是英文compare and swap的缩写,即比较和交换,是由cpu在指令层面保障原子性,提供线程安全的一种解决方案。我们熟系的乐观锁,以及juc包中相关原子类,比如AtomicInteger的底层实现。
关于cas,它具体的实现原理是这样的,它有三个值:内存值A、期望值B、更新值C。每个线程执行更新的时候:
- 它会首先比较内存值A,是否等于期望B
- 如果等于,则将内存值A更新成C,此次cas操作成功
- 如果不等于,则说明已经被其它线程更新过了,那么此次cas操作失败
- 循环进入下次cas操作,直到操作成功为止
文字描述稍微有点抽象,我们看一个图,你就能理解了,看图:
理解了cas的本质以后,我们最后通过一个案例来演示一下cas的实现原理。
2.案例
2.1.模拟cas实现
案例描述:
- 假设初始值value等于0,即cas三个值中的内存值
- 定义doCas方法,方法参数分别是cas三个值中的期望值,与更新值
- doCas方法逻辑比较简单,将期望值expectedValue,与内存值value进行比较
- 如果相等,cas操作成功,将内存值更新为updateValue
- 如果不相等,cas操作失败,内存值此次不做更新
- 在main方法中,创建两个线程:thread_1、thread_2
- thread_1执行cas操作:doCas(0, 1),将内存值更新为1
- thread_2执行cas操作:doCas(0, 2),将内存值更新为2
- 预期执行结果:两个线程,有且仅有一个线程更新成功
package com.anan.edu.common.newthread.atomic;
/**
* 模拟cas实现原理
*
* @author ThinkPad
* @version 1.0
* @date 2020/11/15 17:07
*/
public class CasDemo {
/**
* 初始值为0
*/
private static int value = 0;
/**
* 模拟cas操作:synchronized锁,模拟了底层原子性操作
* @param expectedValue 期望值
* @param updateValue 更新值
* @return 返回内存值
*/
public static synchronized int doCas(int expectedValue, int updateValue){
int oldValue = value;
// 比较内存值,是否等于期望值,如果相等,则更新成功
if(expectedValue == oldValue){
value = updateValue;
System.out.println("线程【" + Thread.currentThread().getName() + "】更新值成功.将值更新为:" + updateValue);
}else{
System.out.println("线程【" + Thread.currentThread().getName() + "】更新值失败.");
}
// 返回内存值
return oldValue;
}
/**
* main方法
* @param args
*/
public static void main(String[] args) throws Exception{
// 1.创建2个线程,并行更新value值
// 线程1,期望将值更新成1
Thread thread_1 = new Thread(() -> { doCas(0, 1); }, "线程_1");
thread_1.start();
// 线程2,期望将值更新成2
Thread thread_2 = new Thread(() -> {doCas(0,2);},"线程_2");
thread_2.start();
// 2.当两个线程执行完成,打印更新成功后的值
thread_1.join();
thread_2.join();
System.out.println("value最后更新的值是:" + value);
}
}
2.2.执行结果及分析
D:\02teach\01soft\jdk8\bin\java com.anan.edu.common.newthread.atomic.CasDemo
线程【线程_1】更新值成功.将值更新为:1
线程【线程_2】更新值失败.
value最后更新的值是:1
Process finished with exit code 0