文章目录
LockSupport 工具类
JDK 中的 rt.jar 包里面的 LockSupport 是个工具类,它的主要作用是挂起和唤醒线程 ,该工具类是创建锁和其他同步类的基础。
LockSupport 类与每 个 使用它的线程都会关联一 个许可证,在 默认情况下调用LockSupport 类的方法的线程是不持有许可证的。 LockSupport 是使用 Unsafe 类实现的 。
park()方法
如果调用 park 方法的线程已经拿到了与 LockSupport 关联的许可证,则调用 Locksupport.park() 时会马上返回,否则调用线程会被禁止参与线程的调度, 也就是会被阻塞挂起。
在其他线程调用 unpark(Thread thread)方法并且将当前线程作为参数时 ,调用 park 方法而被阻塞的线程会返回。另外,如果其他线程调用了阻塞线程的 interrupt() 方法 ,设置了中断标志或者线程被虚假唤醒,则阻塞线程也会返回。所以在调用park 方法时最好也使用循环条件判断方式。
需要注意的是,因调用 park() 方法而被阻塞的线程被其他线程中断而返回 时并不会抛出 InterruptedException 异常。
unpark(Thread thread )方法
当一个线程调用 unpark 时,如果参数 thread 线程没有持有 thread 与 LockSupport 类关联的许可证, 则 让 thread 线程持有。 如果 thread 之前因调用 park()而被挂起,则调用unpark 后,该线程会被唤醒 。 如果 thread 之前没有调用 park ,则 调用 unpark 方法后 , 再调用 park 方法,其会立刻返回 。
AQS 实现原理
AQS实现概要
AbstractQueuedSynchronizer 抽象同步队列简称 AQS ,它是实现同步器的基础组件,并发包中锁的底层就是使用 AQS 实现的 。 所以了解AQS对于了解锁以及其他基于AQS实现的类有很大帮助。以下是AQS类图结构:
由该图可以看到, AQS 是一个 FIFO 的双向队列,其内部通过节点 head 和 tail 记录队首和队尾元素,队列元素的类型为 Node 。 其中 Node 中的 thread 变量用来存放进入 AQS队列里面的线程: Node 节点内部的 SHARED 用来标记该线程是获取共享资源时被阻塞挂起后放入 AQS 队列的, EXCLUSIVE 用来标记线程是获取独占资源时被挂起后放入AQS 队列的 ;waitStatus 记录当前线程等待状态,可以为 CANCELLED (线程被取消了)、SIGNAL ( 线程需要被唤醒)、 CONDITION (线程在条件队列里面等待〉、 PROPAGATE (释放共享资源时需要通知其他节点〕; prev 记录当前节点的前驱节点, next 记录当前节点的后继节点 。
在 AQS 中 维持了 一 个 单 一 的状态信息 state,可以通过 getState 、 setState 、compareAndS etState 函数修改其值 。 对于 Reentran tLock 的 实 现来说, state 可以用 来表示当 前线程获取锁的可重入次数 ;对于 读写锁ReentrantReadWriteLock 来说 , state 的 高 16位表示读状态,也就是获取该读锁的次数,低 16 位表示获取到写锁的线程的可重入次数;对于 semaphore 来说, state 用来表示当前可用信号的 个数:对于 CountDownlatch 来说,state 用 来表示计数器当前的值 。
AQS 有个内 部类 ConditionObject , 用来结合锁实现线程同步 。 ConditionObject 可以直接访问 AQS 对 象 内部的变量,比如 state 状态值和 AQS 队列。 ConditionObject 是条件变量 , 每个条件变量对应 一个 条件队列 (单向链表队列),其用来存放调用条件变量 的await 方法后被阻塞的线程,如类图所示 , 这个条件队列的头、尾元素分别为 firstWaiter 和 lastWaiter 。
对于 AQS 来说,线程同步的关键是对状态值 state 进行操作 。 根据 state 是否属于一个线程,操作 state 的方式分为独占方式和共享方式 。 在独占方式下获取和释放资源使用的方法为 : void acquire(int arg)、 void acquirelnterruptibly(int arg)、 boolean release(int arg) 。
在共享方式下获取和释放资源的方法为: void acquireShared(int arg) 、void acquireSharedinterruptibly(int arg)、 boolean releaseShared(int arg) 。
使用独占方式获取的资源是与具体线程绑定的,就是说如果一个线程获取到了资源,就会标记是这个线程获取到了,其他线程再尝试操作 state 获取资源时会发现当前该资源不是自己持有的,就会在获取失败后被阻塞 。 比如独占锁 ReentrantLock 的实现, 当 一个线程获取了 ReerrantLock 的锁后,在 AQS 内 部会首先使用 CAS 操作把 state 状态值从 0变为 1 ,然后设置当前锁的持有者为当前线程,当该线程再次获取锁时发现它就是锁的持有者 ,则会把状态值从1 变为 2,也就是设置可重入次数,而当另外一个线程获取锁时发现自己并不是该锁的持有者就会被放入 AQS 阻塞队列后挂起 。
对应共享方式的资源与具体线程是不相关的,当多个线程去请求资源时通过 CAS 方式竞争获取资源,当 一个线程获取到了资源后,另 外一个线程再次去获取时如果当前资源还能满足它的需要,则当前线程只需要使用 CAS 方式进行获取即可 。 比如 Semaphore 信号量 , 当一个线程通过 acquire() 方法获取信号量时,会首先看当前信号量个数是否满足需要, 不满足则把当 前线程放入阻塞队列,如果满足则通过 自旋 CAS 获取信号 量。
在独占方式下 , 获取与释放资源的流程如下 :
( 1 )当 一个线程调用 acquire(int arg) 方法获取独占 资源时,会首先使用 tryAcquire 方法尝试获取资源, 具体是设置状态变量 state 的值,成功则直接返回,失败则将当前线程封装为类型为 Node.EXCLUSIVE 的 Node 节点后插入到 AQS 阻塞 队列的尾部,并调用LockSupport.park(this) 方法挂起自己 。
( 2 )当 一个线程调用 re le ase( int arg)方法时会尝试使用 tryRele ase 操作释放资源,这里是设置状态变量 state 的值,然后调用 LockSupport.unpark(thread)方法激活 AQS 队列里面被阻塞的一个线程(thread) 。 被激活的线程则使用 tryAcquire 尝试,看当前状态变量 state的值是否能满足自己的需要,满足则该线程被激活,然后继续 向下运行,否则还是会被放入 AQ S 队列并被挂起 。
需要注意的是 , AQS 类并没有提供可用的句Acquire 和tryRelease 方法,正如 AQS是锁阻塞和同步器的基础框架一样, t可Acquire 和 tryRelease 需要由具体的子类来实现 。子类在实现 tryAcquire 和 tryRelease 时要根据具体场景使用 CAS 算法尝试修改 state 状态值,成功则返 回 true,否则返回 false 。 子类还需要定义,在调用 acquire 和 release 方法时 state状态值的增减代表什么含义 。
比如继承自 AQS 实现的独占锁 ReentrantLock , 定义当 status 为 0 时表示锁空闲,为1 时 表示锁己经被占用 。 在重写tryAcquire 时,在内部 需 要使用 CA S 算法查看当前 state是否为 0,如果为 0 则使用 CAS 设置为 1 ,并设置当前锁的持有者为当前线程,而后返回true , 如果 CAS 失败则返回 false 。
比如继承自 AQS 实现的独占锁在实现 tryRelease时, 在 内 部需要使用 CAS 算法把当前 state 的值从 1 修改为 0 , 并设置当前锁的持有者为 null ,然后返回 true , 如果 CAS 失败则返回 false 。
在共享方式下 ,获取与释放资源的流程如下 :
( 1 )当线程调用 acquireShared(int arg) 获取共享资源时,会首先使用 acquireShared尝试获取资源 , 具体是设置状态变量 state 的 值,成功则 直接返 回,失败则将当前线程 封装为类型为 Node.SHARED 的 Node 节 点后插入 到 AQS 阻 塞 队列的尾部,并使用LockSupport.park(this) 方法挂起自己。
( 2 )当一个线程调用 releaseShared( int arg)时会尝试使用 tryReleaseShared 操作释放资源,这里是设置状态变量 state 的值,然后使用 LockSupport.unpark ( thread )激活 AQS 队列里面被阻塞的一个线程 (thread) 。被激活的线程则使用 tryReleaseShared 查看当前状态变量 state 的值是否能满足自 己的 需要,满足则该线程被撤活,然后继续向下运行,否则还是会被放入 AQS 队列并被挂起 。
同样 需要注意的是, AQS 类并没有提供可用的 tryAcquireShared 和 tryReleaseShared方法,正如 AQS 是锁阻塞和同步器的基础框架一样 , tryAcquireShared 和 tryReleaseShared需要由具体的子类来实现 。 子类在实现 t叩AcquireShared 和 tryReleaseShared 时要根据具体场景使用 CAS 算法尝试修改 state 状态值,成功则返回 true ,否则返回 false 。
比 如继承自 AQS 实现的读写锁 ReentrantReadWriteLock 里面的读锁在重写tryAcquireShared 时,首先查看写锁是否被其他线程持有,如果是则直接返回 false , 否则使用 CAS 递增 state 的高 16 位 (在 ReentrantReadWriteLock 中, state 的 高 16 位为获取读锁的次数) 。
比如继 承自 AQS 实现 的 读写锁ReentrantReadWriteLock 里面的 读锁在重写tryReleaseShared 时,在内部 需要使用 CAS 算法 把 当前 state 值的高 16 位减 1 , 然后返回true ,如 果 CAS 失败则 返回 false 。
基于 AQS 实现的锁除了 需要重写上面介绍的方法外,还需要重写 isHeldExclusively方法,来判断锁是被当前线程独占还是被共享 。
独占方式下的 void acquire(int arg)和 void acquirelnterruptibly(int arg),与共享方式下的 void acquireShared(int arg)和 void acquireSharedlnterruptibly(int arg),这两套函数中都有一个带有 Interruptibly 关键字的函数,其实不带 Intenuptibly 关键字的方法的意思是不对中断进行响应,也就是线程在调用不带 Interruptibly 关键字的方法获取资源时或者