什么是CAS
CAS的全称是: Compare And Swap(比较再交换),它体现的一种乐观锁的思想,在无锁情况下保证线程操作共享数据的原子性。
在JUC( java.util.concurrent )包下实现的很多类都用到了CAS操作
AbstractQueuedSynchronizer(AQS框架)
AtomicXXX类
例子:
一、线程1与线程2都从主内存中获取变量int a = 100,同时放到各个线程的工作内存中
二、线程1操作:V:int a = 100,A:int a = 100,B:修改后的值:int a = 101 (a++)
线程1拿A的值与主内存V的值进行比较,判断是否相等
如果相等,则把B的值101更新到主内存中
三、线程2操作:V:int a = 100,A:int a = 100,B:修改后的值:int a = 99(a--)
线程2拿A的值与主内存V的值进行比较,判断是否相等(目前不相等,因为线程1已更新V的值99)
不相等,则线程2更新失败
在上述例子里:当前内存值V、旧的预期值A、即将更新的值B,当且仅当旧的预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。如果CAS操作失败,通过自旋的方式等待并再次尝试,直到成功
什么是自旋
自旋锁是指当线程尝试获取锁时,如果锁已经被占用,线程不会立即阻塞,而是通过自旋,也就是循环等待的方式不断尝试获取锁。
线程1 线程2
| |
| 获取锁成功 | 尝试获取锁
|------------>|(锁已被占用,自旋等待)
| 释放锁 |
|<------------| 获取锁成功
| |
自旋锁的优点是可以避免线程切换带来的开销,缺点是如果锁被占用时间过长,会导致线程空转,浪费 CPU 资源。
默认情况下,自旋锁会一直等待,直到获取到锁为止。在实际开发中,需要设置自旋次数或者超时时间。如果超过阈值,线程可以放弃锁或者进入阻塞状态。
自旋和阻塞的区别:
CAS 底层实现
CAS 底层依赖于一个 Unsafe 类来直接调用操作系统底层的 CAS 指令
都是native修饰的方法,由系统提供的接口执行,并非java代码实现,一般的思路也都是自旋锁实现
在java中比较常见使用有很多,比如ReentrantLock和Atomic开头的线程安全类,都调用了Unsafe中的方法
ReentrantLock中的一段CAS代码
什么是AQS
全称是Abstract Queued Sychnorizer,即抽象队列同步器。它是构建锁与其他同步组件的基础框架。
AQS 是一个抽象类,它维护了一个共享变量 state 和一个先进先出的线程等待队列,队列中存储的是排队的线程,为 ReentrantLock、Semaphore 等类提供底层支持。
AQS 的思想是,如果被请求的共享资源处于空闲状态,则当前线程成功获取锁;
否则,将当前线程加入到等待队列中,当其他线程释放锁时,从等待队列中挑选一个线程,把锁分配给它
注意:对state修改的时候使用的cas操作,保证多个线程修改的情况下原子性。
什么是Unsafe
Unsafe 是 Java 中一个非常特殊的类,它为 Java 提供了一种底层、"不安全"的机制来直接访问和操作内存、线程和对象。正如其名字所暗示的,Unsafe 提供了许多不安全的操作,因此它的使用应该非常小心,并限于那些确实需要使用这些底层操作的场景。
Unsafe 基础
查看 Unsafe 类的源码,可以发现它是被 final 修饰的,所以不允许被继承,并且构造方法为private类型,即不允许我们直接 new 实例化。不过,Unsafe 在 static 静态代码块中,以单例的方式初始化了一个 Unsafe 对象:
public final class Unsafe {
private static final Unsafe theUnsafe;
...
private Unsafe() {
}
...
static {
theUnsafe = new Unsafe();
}
}
Unsafe 类提供了一个静态方法getUnsafe
,看上去貌似可以用它来获取 Unsafe 实例:
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
但是如果我们直接调用这个静态方法,也会抛出异常:
Exception in thread "main" java.lang.SecurityException: Unsafe
at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
at com.cn.test.GetUnsafeTest.main(GetUnsafeTest.java:12)
这是因为在getUnsafe
方法中,会对调用者的classLoader
进行检查,判断当前类是否由Bootstrap classLoader
加载,如果不是的话就会抛出一个SecurityException
异常。
也就是说,只有启动类加载器加载的类才能够调用 Unsafe 类中的方法,这是为了防止这些方法在不可信的代码中被调用。
为什么要对 Unsafe 类进行这么谨慎的使用限制呢?
说到底,还是因为它实现的功能过于底层,例如直接进行内存操作、绕过 jvm 的安全检查创建对象等等,概括的来说,Unsafe 类实现的功能可以被分为下面 8 类:
总结
什么是CAS
CAS全称是compare and swap比较再交换,它体现的是一种乐观锁的思想,在无锁状态下保证操作数据的原子性。
CAS用到的地方有:AQS框架、AtomicXXX类。
在操作共享变量的时候使用了自旋锁,效率上更高一些。
CAS底层调用的是Unsafe类方法,都是操作系统提供的,其他语言实现。
乐观锁与悲观锁的区别
CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。