一、什么是CAS
CAS: Compare and Swap,翻译成比较并交换。 执行函数CAS(V,E,N)
CAS有3个操作数,内存值V,旧的预期值E,要修改的新值N。当且仅当预期值E和内存值V相同时,将内存值V修改为N,否则什么都不做。
保证线程安全性的问题 原子性
V: 全局共享变量 原子类中 value值 ===0
E: 旧的预期值
N: 修改的新值
CAS:无锁----自旋控制乐观锁
缺点:自旋 会消耗的cpu的资源 cas 死循环 空转问题 cpu飙高
底层基于unsafe类实现
This === atomicInteger 对象;
Valueoffset == 值偏移量;
// paramObject=atomicInteger 对象
// Valueoffset ==值偏移量;
// paramInt=1
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt) {
// 循环目的 就是为了cas的重试
while (true) {
// paramObject=atomicInteger 对象
// Valueoffset == 值偏移量;
// 获取当前的atomicInteger 中 value值
int i = getIntVolatile(paramObject, paramLong);==i==000
if (compareAndSwapInt(paramObject, paramLong, i, i + paramInt))
return i;
}
}
二、同步之原子类(Atomic类)
Java并发包类库
原子类 cas
比如我们所学习的:原子类同步之原子类(Atomic类)
1. 原子更新基本类型类 cas
AtomicBoolean: 原子更新布尔类型。
AtomicInteger: 原子更新整型。
AtomicLong: 原子更新长整型。
2. 原子更新数组
AtomicIntegerArray: 原子更新整型数组里的元素。
AtomicLongArray: 原子更新长整型数组里的元素。
AtomicReferenceArray: 原子更新引用类型数组里的元素。
3. 原子更新引用类型
AtomicReference: 原子更新引用类型。
AtomicReferenceFieldUpdater: 原子更新引用类型的字段。
AtomicMarkableReferce: 原子更新带有标记位的引用类型,可以使用构造方法更新一个布尔类型的标记位和引用类型。
4. 原子更新字段类
AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。
AtomicLongFieldUpdater: 原子更新长整型字段的更新器。
AtomicStampedFieldUpdater: 原子更新带有版本号的引用类型。
5. JDK8新增原子类
DoubleAccumulator
LongAccumulator
DoubleAdder
LongAdder
Java 层面 Unsafe实现
三、使用atomicInteger计数
atomicInteger 计数能够保证线程安全问题。
public class Thread01 extends Thread {
// private static int sum = 0;
private static AtomicInteger atomicInteger = new AtomicInteger(0);
@Override
public void run() {
sum();
}
public void sum() {
for (int i = 0; i < 10000; i++) {
// sum++;
atomicInteger.incrementAndGet();
}
}
public static void main(String[] args) throws InterruptedException {
Thread01 t1 = new Thread01();
Thread01 t2 = new Thread01();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("sum:" + atomicInteger.get());
}
}
四、使用atomicInteger底层原理
atomicInteger底层基于CAS实现
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
Unsafe类,全限定名是sun.misc.Unsafe,从名字中我们可以看出来这个类对普通程序员来说是“危险”的,一般应用开发者不会用到这个类。
Unsafe类是"final"的,不允许继承。且构造函数是private的
传递三个参数 【this(atomicInteger this) valueOffset(成员变量在内存中偏移量) ,1 】+1
Value=对象内存地址+内存偏移量
CAS前置知识Unsafe
Unsafe对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得 java9已经移除Unsafe
Cas底层 Unsafe类实现-----java层面
Cas 能够保证线程安全性 数据的原子性------cpu硬件支撑
private static AtomicInteger atomicInteger = new AtomicInteger();
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
// 1.使用反射获取theUnsafe属性
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
// 2.获取到unsafe
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
// 1.获取域的偏移地址
long idOffset = unsafe.objectFieldOffset(Lock.class.getDeclaredField("state"));
Lock lock = new Lock();
boolean result = unsafe.compareAndSwapInt(lock, idOffset, 0, 1);
System.out.println((ClassLayout.parseInstance(lock).toPrintable()));
System.out.println(result);
}
static class Lock {
int state;
}
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt) {
while (true) {
int i = getIntVolatile(paramObject, paramLong);
if (compareAndSwapInt(paramObject, paramLong, i, i + paramInt))
return i;
}
}
从对象的内存处开始,获得原始字节偏移量,用于存储实力对象的内存
在这里偏移量的意思就像我们 new 一个数组,数组的地址就是数组地一个元素的地址,假如数组地址是 a,第二个元素就是a+1,其中+1就是偏移量。对应的对象的一个属性的偏移量就是其对象的地址开始增加,增加的数就是这个filed的偏移量。
对于VALUE这个值我们知道了,他是AtomicInteger中的value属性对应的偏移量,就是对象地址+VALUE = value的地址
循环:获取当前atomicInteger 原子类的 value=0 i=0
compareAndSwapInt(1,1,1+1);
AtomicInteger 底层如何实现
1.创建了一个AtomicInteger 类
2.先定义一个 int类型value值
3.底层都是基于unsafe类实现----
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// 参数1 this new AtomicInteger
// 参数2 valueOffset
// 参数3 1
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// var5
var5 = this.getIntVolatile(var1, var2);
//this.compareAndSwapInt(var1, var2, var5, var5 + var4)
// var1 this new AtomicInteger 对象
// var2 value的值 offset
// var5---当前value值=0 (旧预期值 E)
// var5(0) + var4(1)---当前value值+1
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
五、compareAndSet原理分析
compareAndSet(e,n);
E==V 则将N的值赋值给V;
1. 手写AtomicInteger
public class DemoAtomicInteger {
// 设定v=0
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public void incrementAndGet() {
// Integer v = atomicInteger.get();
Integer e = atomicInteger.get();
// 一直重试不断循环 如果cas 失败的 则一直不断重试
for (; ; ) {
if (atomicInteger.compareAndSet(e, e + 1)) {
return;
}
}
}
public static void main(String[] args) {
DemoAtomicInteger demoAtomicInteger = new DemoAtomicInteger();
demoAtomicInteger.incrementAndGet();
demoAtomicInteger.incrementAndGet();
demoAtomicInteger.incrementAndGet();
System.out.println(atomicInteger.get());
}
}
2. 手写Lock锁
Aqs+cas实现
默认锁的状态是为=0 如果获取到锁 则锁的状态就是为1
public class DemoLock {
/**
* 默认锁的状态是为=0 如果获取到锁 则锁的状态就是为1
*/
private AtomicInteger lockState = new AtomicInteger(0);
private Thread cuLockThread;
/**
* 获取锁
*/
public void lock() {
for (; ; ) {
if (lockState.compareAndSet(0, 1)) {
return;
}
}
}
/**
* 释放锁
*/
public void unLock() {
for (; ; ) {
if (lockState.compareAndSet(1, 0)) {
cuLockThread = null;
return;
}
}
}
public static void main(String[] args) {
DemoLock demoLock = new DemoLock();
System.out.println("3");
}
}
3. CAS aba的问题
Cas主要检查 内存值V与旧的预值值=E是否一致,如果一致的情况下,则修改。
这时候会存在ABA的问题:
如果将原来的值A,改为了B,B有改为了A 发现没有发生变化,实际上已经发生了变化,所以存在Aba问题。
解决办法:通过版本号码,对每个变量更新的版本号码做+1
解决aba问题是否大:概念产生冲突,但是不影响结果,换一种方式 通过版本号码方式。
4. AtomicMarkableReference
(1)第一个参数expectedReference:表示预期值。
(2)第二个参数newReference:表示要更新的值。
(3)第三个参数expectedStamp:表示预期的时间戳。
(4)第四个参数newStamp:表示要更新的时间戳。
public class Test004 {
// 注意:如果引用类型是Long、Integer、Short、Byte、Character一定一定要注意值的缓存区间!
// 比如Long、Integer、Short、Byte缓存区间是在-128~127,会直接存在常量池中,而不在这个区间内对象的值则会每次都new一个对象,那么即使两个对象的值相同,CAS方法都会返回false
// 先声明初始值,修改后的值和临时的值是为了保证使用CAS方法不会因为对象不一样而返回false
private static final Integer INIT_NUM = 1000;
private static final Integer UPDATE_NUM = 100;
private static final Integer TEM_NUM = 200;
private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(INIT_NUM, 1);
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
Integer value = (Integer) atomicStampedReference.getReference();
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + " : 当前值为:" + value + " 版本号为:" + stamp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// value 旧值 内存中的值 UPDATE_NUM 修改的值
if (atomicStampedReference.compareAndSet(value, UPDATE_NUM, 1, stamp + 1)) {
System.out.println(Thread.currentThread().getName() + " : 当前值为:" + atomicStampedReference.getReference() + " 版本号为:" + atomicStampedReference.getStamp());
} else {
System.out.println("版本号不同,更新失败!");
}
}
}, "线程A").start();
}
}
六、CAS 优点
CAS 优点: 避免用户态到内核态切换
缺点: 非常消耗cpu的资源