Lock 接口的核心概念很简单, 只有如下几个方法
按照逻辑可以进行如下划分
lock()
Lock 接口, 所以 synchronized 关键字更为灵活的一种同步方案, 在实际使用中, 自然是能够替代 synchronized 关键字的
(ps: 尽管你不需要总是使用显式锁, 显式锁与隐式锁各有利弊, 但是在语法上是的确可以替代的)
synchronized 关键字是阻塞式的获取锁
lock 方法就是这一逻辑的体现, 也就是说对于 lock()方法, 如果获取不到锁, 那么将会进入阻塞状态, 与 synchronized 关键字一样
lockInterruptibly()
Lock()方法是一种阻塞式的, 另外 Lock 接口还提供了可中断的 lock 获取方法, 先看下测试例子
- package test2;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- public class T28 {
- private static final Lock LOCK = new ReentrantLock();
- public static void main(String[] args) {
- // 线程 A 获取加锁之后, 持有五秒钟
- Thread threadA = new Thread(() -> {
- LOCK.lock();
- try {
- System.out.println(Thread.currentThread().getName() + " " + System.currentTimeMillis());
- System.out.println(Thread.currentThread().getName() + "sleep");
- TimeUnit.SECONDS.sleep(10);
- System.out.println(Thread.currentThread().getName() + " " + System.currentTimeMillis());
- } catch (InterruptedException e) {
- System.out.println(Thread.currentThread().getName() + "interrupt");
- } finally {
- LOCK.unlock();
- }
- }, "thread-A");
- threadA.start();
- // 线程 B 开始后, 尝试获取锁
- Thread threadB = new Thread(() -> {
- LOCK.lock();
- try {
- System.out.println(Thread.currentThread().getName() + " " + System.currentTimeMillis());
- System.out.println(Thread.currentThread().getName() + "working");
- } finally {
- LOCK.unlock();
- }
- }, "thread-B");
- threadB.start();
- // 为了确保上面的任务都开始了, 主线程 sleep 1s
- try {
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- }
- threadB.interrupt();
- }
- }
示例逻辑
两个线程 A 和 B, 使用同一把锁
A 线程获取锁后, 休眠 10s, 紧接着 B 尝试获取锁
为了保证前面的任务都开始了, 主线程 sleep 1s 后, 将线程 B 进行中断
对于 lock 方法, 如同 synchronized 关键字, 是阻塞式的, 通过执行来看, 可以发现, 在 A 持有锁期间, 线程 B 也是一直阻塞的, 是不能够获取到锁, 也不能被中断 (上面示例中调用 interrupt() 没有任何的反应)
将代码稍作修改, 也就是将 lock 方法修改为 lockInterruptibly()方法, 其他暂时不变
再次运行, 你会发现马上就被中断了, 而不是傻傻的等待 A 结束
当然, 因为根本都没有获取到锁, 所以在 finally 中尝试 unlock 时, 将会抛出异常, 这个暂时不管了, 通过这个例子可以看得出来
对于 lockInterruptibly 方法, 这是一个 "可中断的锁获取操作"
小结
lockInterruptibly 就是一个可中断的锁获取操作, 在尝试获取锁的过程中, 如果不能够获取到, 如果被中断, 那么它将能够感知到这个中断, 而不是一直阻塞下去
如果锁不可用(被其他线程持有), 除非发生以下事件, 否则将会等待
该线程成功获得锁
发生中断
如果当前线程遇到下面的事件, 则将抛出 InterruptedException, 并清除当前线程的已中断状态.
在进入此方法时已经设置了该线程的中断状态
在获取锁时被中断
从上面的分析可以看得出来, 如果什么都没发生, 这个方法与 lock 方法并没有什么区别, 就是在等待获取锁, 获取不到将会阻塞
他只是额外的对可中断提供了支持
unlock()
unlock 并没有什么特殊的, 他替代了 synchronized 关键字隐式的解锁操作
通常需要在 finally 中确保 unlock 操作会被执行, 之前提到过, 对于 synchronized 关键字解锁是隐式的, 也是必然的, 即使出现错误, JVM 也会保障能够正确的解锁
但是对于 Lock 接口提供的 unlock 操作, 则必须自己确保能够正确的解锁
tryLock()
相对于 synchronized,Lock 接口另一大改进就是 try lock
顾名思义, 尝试获取锁, 既然是尝试, 那显然并不会势在必得
tryLock 方法就是一次尝试, 如果锁可用, 则获取锁, 并立即返回值 true. 如果锁不可用, 则此方法将立即返回值 false
也就是说方法会立即返回, 如果获取到锁返回 true, 否则返回 false, 不管如何都是立马返回
典型的用法就是如下所示, 下面的代码还能够确保如果没有获取锁, 不会试图进行 unlock 操作
- Lock lock = ...;
- if (lock.tryLock()) {
- try {
- // manipulate protected state
- } finally {
- lock.unlock();
- }
- } else {
- // perform alternative actions
- }
tryLock 只是一次尝试, 如果你需要不断地进行尝试, 那么可以使用 while 替代 if 的条件判断
尽管 tryLock 只是一次的测试, 但是可以借助于循环 (有限或者无限) 进行多次测试
tryLock(long time, TimeUnit unit)
对于 TryLock 还有可中断, 配置超时时间的版本
- boolean tryLock(long time,
- TimeUnit unit)
- throws InterruptedException
两个参数, 第一个为值, 第二个为第一个参数的单位, 比如 1, 单位秒, 或者 2 , 单位分钟
在指定的超时时间内, 如果能够获取到锁, 那么将会返回 true;
如果超过了指定的时间, 但是却不能获取到锁, 那么将会返回 false;
另外很显然, 这个方法是可中断的, 也就是说如果尝试过程中, 出现了中断, 那么他将会抛出 InterruptedException
所以, 对于这个方法, 他会一直尝试获取锁(也可以认为是一定时长内的 "阻塞", 当然可以被中断), 除非:
该线程成功获得锁
超过了超时时长
该线程被中断
可以认为是 lockInterruptibly 的限时版本
如果没有发生中断, 也认为他就是 "定时版本的 lock()"
不管怎么理解, 只需要记住: 他会在一定时长内尝试进行锁的获取, 也支持中断
锁小结
对于 lock 方法和 unlock 方法, 就是类似于 synchronized 关键字的加锁和解锁, 并没有什么特别的
其他几个方法是 Lock 接口针对于锁获取的阻塞以及可中断两个方面进行了拓展
隐式锁的阻塞以及不可中断, 导致一旦开始尝试获取, 那么则没办法唤醒, 将会一直等待, 除非获得
lockInterruptibly()是阻塞式的, 如果获取不到会一直等待, 但是他是可中断的, 能够通过阻塞打破这种等待
tryLock()不会进行任何阻塞, 只是尝试获取一下, 能获取到就获取, 获取不到就 false, 拉倒
tryLock(long time, TimeUnit unit), 即是可中断的, 又是限时阻塞的, 即使不中断, 也不会一直阻塞, 即使处于阻塞中(超时时长还没到), 也可以随时中断
对于 lockInterruptibly()方法以及 tryLock(long time, TimeUnit unit), 都支持中断, 但是需要注意:
在某些实现中可能无法中断锁获取, 即使可能, 该操作的开销也很大
Condition
在隐式锁的逻辑中, 借助于 Java 底层机制, 每个对象都有一个相关联的锁与监视器
对于 synchronized 的隐式锁逻辑就是借助于锁与监视器, 从而进行线程的同步与通信协作
在显式锁中, Lock 接口提供了 synchronized 的语意, 对于监视器的概念, 则借助于 Condition, 但是很显然, Condition 也是与锁关联的
Lock 接口提供了方法 Condition newCondition();
Condition 也是一个接口, 他定义了相关的监视器方法
在显式锁中, 可以定义多个 Condition, 也就是一个锁, 可以对应多个监视器, 可以更加细粒度的进行同步协作的处理
总结
Lock 接口提供了相对于 synchronized 关键字, 而更为灵活的一种同步手段
它的核心与本质仍旧是为了线程的同步与协作通信
所以它的核心仍旧是锁与监视器, 也就是 Lock 接口与 Condition 接口
但是灵活是有代价的, 所以并不需要在所有的地方都尝试使用显式锁, 如果场景满足需要, synchronized 仍旧是一种很好的解决方案(也是应该被优先考虑的一种方式)
与 synchronized 再次对比下
synchronized 是 JVM 底层实现的, Lock 是 JDK 接口层面的
synchronized 是隐式的, Lock 是显式的, 需要手动加锁与解锁
synchronized 乌无论如何都会释放, 即使出现错误, Lock 需要自己保障正确释放
synchronized 是阻塞式的获取锁, Lock 可以阻塞获取, 可中断, 还可以尝试获取, 还可以设置超时等待获取
synchronized 无法判断锁的状态, Lock 可以进行判断
synchronized 可重入, 不可中断, 非公平, Lock 可重入, 可中断, 可配置公平性(公平和非公平都可以)
如果竞争不激烈, 两者的性能是差不多的, 可是 synchronized 的性能还在不断的优化, 当竞争资源非常激烈时(即有大量线程同时竞争), 此时 Lock 的性能要远远优于 synchronized
等
对于 Lock 接口, 他仍旧是一个对象, 所以他是否可以用来作为锁以及调用监视器方法 (用在 synchronized(lock) 中)?
这逻辑上是没问题的, 但是最好不要那么做, 因为很容易引起混淆的, 不管是维护上还是易读性上都有很大的问题
在 lock 上调用他的监视器方法, 与借助于 lock 实现线程的同步, 本质上是没有什么关系的
尽管看起来 Lock 是那么的优秀, 但是还是要再次提醒, 除非 synchronized 真的不行, 否则你应该使用 synchronized 而不是 Lock
来源: https://www.cnblogs.com/noteless/p/10481286.html