ReentrantLock?
synchronized
是托管给JVM执行的,Lock
的锁定是通过代码实现
的。所以Lock比较灵活,可以便于开发人员根据合适的场景进行操作,Lock是一个接口,需要实现它来进行使用,ReetrantLock
是Lock的主要实现类,ReetrantLock是一个可重入锁
,同时可以指定公平锁
和非公平锁
,我们来具体看一下他的实现方式。
什么是可重入锁?
为什么叫重入锁呢?
ReentrantLock
,我们把它拆开来看就明了了。
Re
-Entrant
-Lock
:即表示可重新反复进入的锁,但仅限于当前线程;
public void m() {
lock.lock();
lock.lock();
try {
// ... method body
} finally {
lock.unlock()
lock.unlock()
}
}
如示例代码所示,当前线程可以反复加锁
,但也需要释放同样加锁次数
的锁,即重入了多少次,就要释放多少次,不然也会导入锁不被释放。
试想一下,如果不设计成可重入锁,那自己如果反复给自己加锁,不是会把自己加死锁了吗?所以,到现在,重入锁的概念大概应该清楚了吧?
重入锁最重要的几个方法
这几个方法都是 Lock 接口中定义的:
1. lock()
获取锁,有以下三种情况:
- 锁空闲:直接获取锁并返回,同时设置锁持有者数量为:1;
- 当前线程持有锁:直接获取锁并返回,同时锁持有者数量递增1;
- 其他线程持有锁:当前线程会休眠等待,直至获取锁为止;
2. lockInterruptibly()
获取锁,逻辑和 lock() 方法一样,但这个方法在获取锁过程中能响应中断。
3. tryLock()
从关键字字面理解,这是在尝试获取锁,获取成功返回:true,获取失败返回:false, 这个方法不会等待,有以下三种情况:
- 锁空闲:直接获取锁并返回:true,同时设置锁持有者数量为:1;
- 当前线程持有锁:直接获取锁并返回:true,同时锁持有者数量递增1;
- 其他线程持有锁:获取锁失败,返回:false;
4)tryLock(long timeout, TimeUnit unit)
逻辑和 tryLock() 差不多,只是这个方法是带时间的。
5)unlock()
释放锁,每次锁持有者数量递减 1,直到 0 为止。所以也就是为什么 lock 多少次,就要对应 unlock 多少次。
6)newCondition
返回一个这个锁的 Condition 实例,可以实现 synchronized 关键字类似 wait/ notify 实现多线程通信的功能,不过这个比 wait/ notify 要更灵活,更强大!
代码示例:
最简单的加锁
private static void add() {
reentrantLock.lock();
try {
count++;
} finally {
reentrantLock.unlock();
}
}
加锁和释放锁都在方法里面进行,可以自由控制,比 synchronized 更灵活,更方便。但要注意的是,释放锁操作必须在 finally 里面,不然如果出现异常导致锁不能被正常释放,进而会卡死后续所有访问该锁的线程。
ReentrantLock
和Condition
组件搭配使用:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class LockExample6 {
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();
Condition condition = reentrantLock.newCondition();
new Thread(() -> {
try {
reentrantLock.lock(); // 此时被加入到了AQS等待序列中
log.info("wait signal"); // 1
condition.await(); // 又被AQS移除了(await操作释放了锁) 进入了Condition的等待队列中
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("get signal"); // 4 获得信号
reentrantLock.unlock();
}).start();
new Thread(() -> {
reentrantLock.lock(); // 获得锁 进入AQS等待序列中
log.info("get lock"); // 2
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
condition.signalAll(); // 发送信号唤醒所有等待的线程 将Condition等待队列中的线程1节点的 取出并加入到AQS等待队列中
log.info("send signal ~ "); // 3
reentrantLock.unlock();
}).start();
}
}