Lock
- 是一种用于控制多个线程对共享资源的访问工具
一、Lock使用
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long var1, TimeUnit var3) throws InterruptedException;
void unlock();
Condition newCondition();
}
它是个接口类,有6个方法,前面4个就是用来获取锁的,第5个用来释放锁的,第6个用来设置等待和通知的。
1. lock():普通获取锁的方法
2. lockInterruptibly():通过这个方法获取锁的话,如果线程正在等待获取锁,则这个线程能够相应中断,即中断线程的等待状态。
3. tryLock():具有返回值,得到锁返回true,否则返回false。
4. tryLock(long var1, TimeUnit var3):跟上面一样也有返回值,多了等待时间,第一个参数是要等待的时间,第二个参数是时间单位。
5. unlock():释放锁
6. newCondition():设置等待和通知的
- lock使用
private Lock lock = new ReentrantLock();
public void testLock(String name) {
lock.lock();
System.out.println(Thread.currentThread().getName() + ",Request Lock...");
try {
for (int i = 0; i < 100; i++) {
y++;
System.out.println(name + "-->CurrThread:"
+Thread.currentThread().getName() + " y=" + y);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName()
+ ",Release Lock ...");
}
}
二、Lock原理
因为ReentrantLock实现了Lock接口,我们可以观察ReentrantLock怎么实现。
经观察ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,该类继承了AbstractQueuedSynchronizer(一般简称AQS)
static abstract class Sync extends AbstractQueuedSynchronizer
Sync又有两个子类:(区别实现公平锁和非公平锁)
final static class NonfairSync extends Sync
final static class FairSync extends Sync
ReentrantLock的公平锁和非公平锁的实现差异主要体现在 获取锁时的策略 上,其底层基于 AbstractQueuedSynchronizer(AQS)框架。
| 行为 | 公平锁(FairSync) | 非公平锁(NonfairSync) |
| 锁获取策略 | 严格按队列顺序(先到先得) | 允许新线程插队抢占 |
| 性能 | 低(上下文切换频繁) | 高(减少线程挂起和唤醒) |
| 饥饿问题 | 避免饥饿 | 可能引发线程饥饿 |
| 实现差异 |
中检查队列 |
直接尝试 CAS,不检查队列 |
AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。

CLH解释:(Craig,Landin,and Hagersten locks)是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱的释放了锁就结束自旋。
- 核心数据结构:双向链表 + state(锁状态)
- 底层操作:CAS
AQS中的int类型的state值,这里就是通过CAS(乐观锁)去修改state的值。lock的基本操作还是通过乐观锁来实现的。
1.获取锁通过CAS修改state的值,
2.如果是没有获取到锁,会尝试再次通过CAS获取一次锁,还是没有的话,会将当前线程加入到等待队列中。当当前线程到头部的时候,尝试CAS更新锁状态,如果更新成功表示该等待线程获取成功,从头部移除。
public final void acquire(int arg) {
/***
* 1.tryAcquire:会尝试再次通过CAS获取一次锁。
* 2.addWaiter:通过自旋CAS,将当前线程加入上面锁的双向链表(等待队列)中。
* 3.acquireQueued:通过自旋,判断当前队列节点是否可以获取锁。
***/
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
3.释放锁就是对AQS中的状态值State进行修改,同时更新下一个链表中的线程等待节点。
通过上述实现过程可以看出,lock大量使用CAS+自旋。

备注:
- 1.为什么有了synchronized还需要Lock?
(1)效率低:锁的释放情况少,试图获得锁时不能设定超时,不能中断一个正在试图获取/释放锁的进程。
(2)不够灵活:加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的
(3)无法知道是否成功获取到锁
- 2.synchronized与Lock区别?
| 区别类型 | synchronized | Lock |
| 本身 | 是java中的关键字 | 是一个接口 |
| 使用范围 | 可以用在方法上,也可以用在特定代码块上 | 需要指定起始位置和终止位置 |
| 实现方式 | 托管给JVM执行的 | 通过代码实现的 |
| 是否支持超时 | 永久阻塞等待 | 支持超时获取锁,超时后释放资源 |
| 性能上 | 在竞争不是很激烈的情况下,synchronized的性能优于ReentrantLock(Lock实现类) | 竞争激烈的情况下synchronized的性能下降的非常快,而ReentrantLock(Lock)则基本不变 |
| 锁机制 | 获取锁和释放锁的方式都是在块结构中,当获取多个锁时,必须以相反的顺序释放锁,并且是自动解锁(发生异常时会自动执行) | 需要开发人员手动释放,并且必须在finally中释放,否则会引起死锁(没有主动释放会造成死锁现象) |
更多java基础总结(适合于java基础学习、java面试常规题):
总结篇(9)---字符串及基本类 (1)字符串及基本类之基本数据类型
总结篇(10)---字符串及基本类 (2)字符串及基本类之java中公共方法及操作
总结篇(12)---字符串及基本类 (4)Integer对象
总结篇(14)---JVM(java虚拟机) (1)JVM虚拟机概括
总结篇(15)---JVM(java虚拟机) (2)类加载器
总结篇(16)---JVM(java虚拟机) (3)运行时数据区
总结篇(17)---JVM(java虚拟机) (4)垃圾回收
总结篇(18)---JVM(java虚拟机) (5)垃圾回收算法
总结篇(19)---JVM(java虚拟机) (6)JVM调优
总结篇(24)---Java线程及其相关(2)多线程及其问题
总结篇(25)---Java线程及其相关(3)线程池及其问题
总结篇(26)---Java线程及其相关(4)ThreadLocal
总结篇(27)---Java并发及锁(1)Synchronized
总结篇(31)---JUC工具类(1)CountDownLatch
本文详细介绍了Lock接口的功能和使用方法,包括其提供的多种获取锁的方式及其实现原理。对比了Lock与synchronized关键字的区别,并探讨了为何需要Lock。
170万+






