如果你熟悉 synchronized,你知道程序编译成 CPU 指令后,在临界区会有 moniterenter 和 moniterexit 指令的出现,可以理解成进出临界区的标识
从范式上来看:
lock.lock() 获取锁,“等同于” synchronized 的 moniterenter指令
lock.unlock() 释放锁,“等同于” synchronized 的 moniterexit 指令
那 Lock 是怎么做到的呢?
这里先简单说明一下,这样一会到源码分析时,你可以远观设计轮廓,近观实现细节,会变得越发轻松
其实很简单,比如在 ReentrantLock 内部维护了一个 volatile 修饰的变量 state,通过 CAS 来进行读写(最底层还是交给硬件来保证原子性和可见性),如果CAS更改成功,即获取到锁,线程进入到 try 代码块继续执行;如果没有更改成功,线程会被【挂起】,不会向下执行
但 Lock 是一个接口,里面根本没有 state 这个变量的存在:
它怎么处理这个 state 呢?很显然需要一点设计的加成了,接口定义行为,具体都是需要实现类的
Lock 接口的实现类基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的
那什么是队列同步器呢? (这应该是你见过的最强标题党,聊了半个世纪才入正题,评论区留言骂我)
队列同步器 AQS队列同步器 (AbstractQueuedSynchronizer),简称同步器或AQS,就是我们今天的主人公
问:为什么你分析 JUC 源码,要从 AQS 说起呢?
答:看下图
相信看到这个截图你就明白一二了,你听过的,面试常被问起的,工作中常用的
ReentrantLock
ReentrantReadWriteLock
Semaphore(信号量)
CountDownLatch
公平锁
非公平锁
ThreadPoolExecutor (关于线程池的理解,可以查看 为什么要使用线程池? )
都和 AQS 有直接关系,所以了解 AQS 的抽象实现,在此基础上再稍稍查看上述各类的实现细节,很快就可以全部搞定,不至于查看源码时一头雾水,丢失主线
上面提到,在锁的实现类中会聚合同步器,然后利同步器实现锁的语义,那么问题来了:
为什么要用聚合模式,怎么进一步理解锁和同步器的关系呢?
我们绝大多数都是在使用锁,实现锁之后,其核心就是要使用方便
从 AQS 的类名称和修饰上来看,这是一个抽象类,所以从设计模式的角度来看同步器一定是基于【模版模式】来设计的,使用者需要继承同步器,实现自定义同步器,并重写指定方法,随后将同步器组合在自定义的同步组件中,并调用同步器的模版方法,而这些模版方法又回调用使用者重写的方法
我不想将上面的解释说的这么抽象,其实想理解上面这句话,我们只需要知道下面两个问题就好了
哪些是自定义同步器可重写的方法?
哪些是抽象同步器提供的模版方法?
同步器可重写的方法同步器提供的可重写方法只有5个,这大大方便了锁的使用者:
按理说,需要重写的方法也应该有 abstract 来修饰的,为什么这里没有?原因其实很简单,上面的方法我已经用颜色区分成了两类:
独占式
共享式