ReentrantLock,独占式锁,支持一个线程对资源的重复加锁,再次加锁时不会阻塞自己(synchronized
关键字隐式地支持锁重入)。此外,它还支持获取锁时选择公平或非公平模式。
那么,何为公平锁?指在绝对时间上,先申请锁的请求一定先被满足,那么这个锁是公平的;反之,是不公平的。可见,获取公平锁时,按照FIFO原则,在同步队列中等待时间最长的线程最先获取锁。
1 创建ReentrantLock
无参构造器默认使用非公平锁。入参为true时使用公平锁。
2 同步器实现
在ReentrantLock中,内部类Sync
继承AbstractQueuedSynchronizer
,作为同步组件。它有两个子类:NonfairSync、FairSync,分别是非公平锁、公平锁的实现。
3 加锁
分别来看公平、非公平两种模式:
- 非公平模式时,
lock()
时先CAS尝试获取锁,如果成功则退出,不用进入同步队列等待。 - 公平模式时,
lock()
时直接走AQS的acquire()
,如果tryAcquire(arg)
失败,线程将进入同步队列等待。
4 锁重入
4.1 加锁
锁重入的实现在ReentrantLock.Sync#nonfairTryAcquire
方法中,关键在于:当同步状态不可获取时,锁需要能够识别当前线程是否是锁持有者。
整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
锁对象如何记录锁持有者呢?AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer
类,后者的exclusiveOwnerThread
属性,能够记录持有独占式锁的线程。
当线程重复获得锁时,对state计数自增即可。
4.2 释放锁
tryRelease(int releases)
方法中:
- 当线程对锁重入了n次后,前n-1次释放锁时,state计数自减,返回false,并未真正失去锁;
- 第n次释放锁时,计数等于0,方法返回true,才会唤醒阻塞中的后继线程。
5 公平与非公平
区别在于:
- 公平模式时,申请资源的(多个)新线程到来时,即使同步状态可被获取,如果队列中有等候的线程,当前线程也不能尝试去获取,必须将添加到队尾,排队等候。
- 非公平模式时,(多个)新线程申请资源时,可以尝试争抢资源;成功时不用入队,失败时才被添加到队尾排队。
使用非公平锁时,如果一直有新线程到来,可能导致入队很早的线程,很久不能获取到锁,造成饥饿
。但是,它能够减小线程切换次数(新线程有很大概率不用入队阻塞),因而非公平锁有更大的吞吐量。
公平锁必须按照FIFO原则,依次排队来获取锁。因此,公平锁往往没有非公平锁的效率高。但是,它可以减少饥饿
发生的概率,等待越久的线程越是优先获取到锁。
6 使用ReentrantLock来避免死锁
常常有这样的场景:一个线程任务需要同时获得多把锁时,才能顺利执行。
使用synchronized时,很容易造成死锁。如下代码,t1线程在获得lock1后,阻塞在获取lock2;正好有其他线程获取了lock2,阻塞在获取lock1。此时,发生了死锁。
java
代码解读
复制代码
Object lock1 = new Object(); Object lock2 = new Object(); Thread t1 = new Thread(() -> { synchronized (lock1) { log.debug("lock1"); sleep(1); synchronized (lock2) { log.debug("lock2"); // do something } } }, "t1");
如果线程获取更多锁失败时,能够自动释放已经获得的锁,将避免可能发生的死锁问题。
ReentrantLock的tryLock()
方法是非阻塞的,使用它很容易实现这个功能。
java
代码解读
复制代码
ReentrantLock lock1 = new ReentrantLock(); ReentrantLock lock2 = new ReentrantLock(); Thread t1 = new Thread(() -> { while (true) { if (lock1.tryLock()) { log.info("t1 get lock1"); try { // tryLock是非阻塞的 if (lock2.tryLock()) { log.info("t1 get lock2"); try { log.info("t1 do something..."); break; } catch (Exception e) { } finally { lock2.unlock(); } } } catch (Exception e) { } finally { lock1.unlock(); } } } }, "t1");