线程同步
个人博客:www.xiaobeigua.icu
1.5 CAS
CAS(Compare And Swap)是由硬件实现的。
CAS 可以将 read- modify - write (读存写)这类的操作转换为原子操作。
i++自增操作包括三个子操作:
从主内存读取 i 变量值
对 i 的值加 1
再把加 1 之后 的值保存到主内存
1.5.1CAS 原理
在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值(操作起始时读取的值)一样就更新,如果不一样,那就有两种情况:
一种是 重试(自旋)
一种是什么都不做
自旋原理图:
1.5.2 CAS中的 A-B-A问题
CAS 实现原子操作背后有一个假设: 共享变量的当前值与当前线程提供的期望值相同, 就认为这个变量没有被其他线程修改过。
实际上这种假设不一定总是成立.如有共享变量 count = 0
A 线程对 count 值修改为 10
B 线程对 count 值修改为 20
C 线程对 count 值修改为 0
当前线程看到 count 变量的值现在是 0,现在是否认为 count 变量的值没有被其他线程更新呢? 这种结果是否能够接受?
这就是 CAS 中的 ABA 问题,即共享变量经历了 A->B->A 的更新
是否能够接收 ABA 问题跟实现的算法有关。
如果想要规避 ABA 问题,可以为共享变量引入一个修订号(时间 戳), 每次修改共享变量时,相应的修订号就会增加 1. ABA 变量更新过程变量: [A,0] ->[B,1]->[A,2], 每次对共享变量的修改都会导致 修订号的增加,通过修订号依然可以准确判断变量是否被其他线程修改过。
AtomicStampedReference 类就是基于这种思想产生的。
1.6 原子变量类
java 1.5引进原子类,具体在java.util.concurrent.atomic包下,atomic包里面一共提供了13个类,分为4种类型,分别是:原子更新基本类型,原子更新数组,原子更新引用,原子更新属性。原子类也是java实现同步的一套解决方案。
既然已经有了synchronized关键字和lock,为什么还要引入原子类呢?或者什么场景下使用原子类更好呢?
在很多时候,我们需要的仅仅是一个简单的、高效的、线程安全的递增或者递减方案,这个方案一般需要满足以下要求:
1、 简单:操作简单,底层实现简单
2、 高效:占用资源少,操作速度快
3、 安全:在高并发和多线程环境下要保证数据的正确性
对于是需要简单的递增或者递减的需求场景,使用synchronized关键字和lock固然可以实现,但代码写的会略显冗余,且性能会有影响,此时用原子类更加方便。
原子变量类基于CAS实现的, 当对共享变量进行read-modify-write 更新操作时,通过原子变量类可以保障操作的原子性与可见性。对变量 的 read-modify-write 更新操作是指当前操作不是一个简单的赋值,而 是变量的新值依赖变量的旧值,如自增操作i++。
由于volatile只能保证可见性,无法保障原子性, 原子变量类内部就是借助一个 Volatile 变量, 并且保障了该变量的 read-modify-write 操作的原子性, 有时把原子变量类看作增强的 volatile 变量. 原子变量类有 12 个,如图:
分组 | 原子变量类 |
基础数据型 | AtomicInteger, AtomicLong, AtomicBoolean |
数组型 | AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray |
字段更新器 | AtomicIntegerFieldUpdater AtomicLongFieldUpdater, AtomicReferenceFieldUpdate |
引用型 | AtomicReference, AtomicStampedReference, AtomicMarkableReference |
接下来我们将用代码去感受原子类
1.6.1原子类如何使用
上面介绍了原子类有4大类,这里以原子更新基本类型中的AtomicInteger类为例,介绍通用的API接口和使用方法。
首先是几个常用的API:
// 以原子方式将给定值与当前值相加,可用于线程中的计数使用,(返回更新的值)。
int addAndGet(int delta)
// 以原子方式将给定值与当前值相加,可用于线程中的计数使用,(返回以前的值)
int getAndAdd(int delta)
// 以原子方式将当前值加 1(返回更新的值)
int incrementAndGet()
// 以原子方式将当前值加 1(返回以前的值)
int getAndIncrement()
// 以原子方式设置为给定值(返回旧值)
int getAndSet(int newValue)
// 以原子方式将当前值减 1(返回更新的值)
int decrementAndGet() :
// 以原子方式将当前值减 1(返回以前的值)
int getAndDecrement()
// 获取当前值
get()
1.6.2 AtomicLongs使用
使用原子变量类定义一个计数器,用来模拟计算网路请求总数和成功数 失败数
计数器类代码:
public class Indicator {
//构造方法私有化
private Indicator(){}
//定义一个私有的本类静态的对象
private static final Indicator INSTANCE = new Indicator();
//提供一个公共静态方法返回该类唯一实例
public static Indicator getInstance(){
return INSTANCE;
}
//使用原子变量类保存请求总数,成功数,失败数
private final AtomicLong requestCount = new AtomicLong(0); //记录请求总数
private final AtomicLong successCount = new AtomicLong(0); //处理成功总数
private final AtomicLong fialureCount = new AtomicLong(0); //处理失败总数
//有新的请求
public void newRequestReceive(){
requestCount.incrementAndGet();
}
//处理成功
public void requestProcessSuccess(){
successCount.incrementAndGet();
}
//处理失败
public void requestProcessFailure(){
fialureCount.incrementAndGet();
}
//查看总数,成功数,失败数
public long getRequestCount(){
return requestCount.get();
}
public long getSuccessCount(){
return successCount.get();
}
public long getFailureCount(){
return fialureCount.get();
}
}
测试代码:
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
//每个线程就是一个请求,请求总数要加 1
Indicator.getInstance().newRequestReceive();
int num = new Random().nextInt();
if ( num % 2 == 0 ){ //偶数模拟成功
Indicator.getInstance().requestProcessSuccess();
}else { //处理失败
Indicator.getInstance().requestProcessFailure();
}
}
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印结果
System.out.println( Indicator.getInstance().getRequestCount()); //总的请求数
System.out.println( Indicator.getInstance().getSuccessCount()); //成功数
System.out.println( Indicator.getInstance().getFailureCount()); //失败数
}
}
1.6.2 AtomicIntegerArray的基本使用
原子更新数组的常用操作
public class Test {
public static void main(String[] args) {
//1)创建一个指定长度的原子数组
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
System.out.println( atomicIntegerArray ); //[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
//2)返回指定位置的元素
System.out.println( atomicIntegerArray.get(0)); //0
System.out.println( atomicIntegerArray.get(1)); //0
//3)设置指定位置的元素
atomicIntegerArray.set(0, 10);
//在设置数组元素的新值时, 同时返回数组元素的旧值
System.out.println( atomicIntegerArray.getAndSet(1, 11) ); //0
System.out.println( atomicIntegerArray ); //[10, 11, 0, 0, 0, 0, 0, 0, 0, 0]
//4)修改数组元素的值,把数组元素加上某个值
System.out.println( atomicIntegerArray.addAndGet(0, 22) ); //32
System.out.println( atomicIntegerArray.getAndAdd(1, 33)); //11
System.out.println( atomicIntegerArray ); //[32, 44, 0, 0, 0, 0, 0, 0, 0, 0]
//5)CAS 操作
//如果数组中索引值为 0 的元素的值是 32 , 就修改为 222
System.out.println( atomicIntegerArray.compareAndSet(0, 32, 222)); //true
System.out.println( atomicIntegerArray ); //[222, 44, 0, 0, 0, 0, 0, 0, 0, 0]
System.out.println( atomicIntegerArray.compareAndSet(1, 11, 333)); //false
System.out.println(atomicIntegerArray);
//6)自增/自减
System.out.println( atomicIntegerArray.incrementAndGet(0) ); //223, 相当于前缀
System.out.println( atomicIntegerArray.getAndIncrement(1)); //44, 相当于后缀
System.out.println( atomicIntegerArray ); //[223, 45, 0, 0, 0, 0, 0, 0, 0, 0]
System.out.println( atomicIntegerArray.decrementAndGet(2)); //-1
System.out.println( atomicIntegerArray); //[223, 45, -1, 0, 0, 0, 0, 0, 0, 0]
System.out.println( atomicIntegerArray.getAndDecrement(3)); //0
System.out.println( atomicIntegerArray ); //[223, 45, -1, -1, 0, 0, 0, 0, 0, 0]
}
}
还有一些原子类这里就不多做演示了,感兴趣的可以去面向互联网编程找一下哈哈哈