什么是CAS

33

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 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。