java中Lock锁实现原理

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 关键字的方法获取资源时或者

### Java `Lock` 接口和 `synchronized` 关键字的工作原理 #### 工作原理 `synchronized` 是一种内置的关键字,其工作依赖于 JVM 的监视器(Monitor Lock)。当一个线程进入由 `synchronized` 保护的方法或者代码块时,该线程必须获取到对应的对象才能继续执行。如果此时其他线程已经持有此对象上的,则当前尝试获取的对象会被阻塞直到前一线程释放为止[^1]。 对于 `Lock` 接口而言,这是显式的定机制的一部分,提供了更灵活丰富的功能集。开发者需要手动调用相应的方法去获得以及释放,比如使用 `lock()` 方法加并配合 try-finally 结构确保最终能通过 `unlock()` 来解。这种灵活性允许程序员更好地控制何时何处应用同步逻辑[^5]。 ```java // 使用 synchronized 实现同步 public class SyncExample { private final Object lockObject = new Object(); public void syncMethod() { synchronized (lockObject) { // 同步操作... } } } // 使用 Lock 实现同步 import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockExample { private final Lock lock = new ReentrantLock(); public void lockedMethod() { lock.lock(); try { // 同步操作... } finally { lock.unlock(); // 确保总是会解 } } } ``` #### 主要区别 - **语法差异**: `synchronized` 是作为语言特性存在的关键字,而 `Lock` 则是一个 API 提供者定义的一组类和接口。 - **性能表现**: 尽管早期版本 `synchronized` 被认为效率低下,但从 JDK 1.6 开始经过大量优化之后,在大多数情况下两者之间的差距变得很小甚至可以忽略不计。不过某些特定场景下 `Lock` 可能提供更好的性能选项[^2]。 - **功能性对比**: - `Lock` 支持条件变量、公平策略设置等功能; - `synchronized` 自动管理的状态变化过程,无需担心忘记解等问题发生; - 当遇到异常情况时,`synchronized` 会在退出作用域的同时自动释放持有的,但 `Lock` 需要在 catch 或 finally 块里明确处理这个问题[^3]。 - **适用范围**: - 对于简单的互斥需求来说,`synchronized` 更简洁易读; - 如果项目有复杂多变的需求或是追求更高的定制化程度,则应考虑采用 `Lock`[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值