我正在参与掘金创作者训练营第6期,点击了解活动详情
认识 ReentrantLock(可重入的互斥锁)
JDK5开始,Java中引入一个工具包,专用于处理并发场景 -java.util.concurrent(常说的:JUC),它提供了大量更高级的并发功能,大大简化多线程程序的处理。 下面将介绍替代 synchronized 的 ReentrantLock(可重入互斥锁)。
传统 synchronized 加锁
synchronized关键字用于加锁,但这种锁一是很重,二是获取时必须一直等待,没有额外的尝试机制。
public class Counter { // 定义加法器
private int count;
// 成员方法
public void add(int n) {
synchronized(this) { // 使用 synchronized 加锁处理
count += n;
}
}
}
使用 ReentrantLock 替代
public class Counter {
private final Lock lock = new ReentrantLock(); // 创建私有 ReentrantLock对象 仅限本类使用
private int count;
public void add(int n) {
// 调lock()获取锁
lock.lock();
try {
count += n;
} finally {
// 用完必须释放锁
lock.unlock();
}
}
}
synchronized 与 ReentrantLock 异同❗
-
ReentrantLock 是可重入互斥锁,与 synchronized 一样,同一线程可多次获取同一个锁对象
-
与 synchronized 不同的是,ReentrantLock 可尝试获取锁:tryLock(arg1, arg2) 方法
if (lock.tryLock(1, TimeUnit.SECONDS)) { // 调用 tryLock() 尝试获取
try {
...
} finally {
lock.unlock(); // 释放锁
}
}
上述代码 调用tryLock()在尝试获取锁对象,最多等待1秒。 若1秒后仍未获取到锁,tryLock() 即返回false,程序即进行其他处理,并不是无限等待下去。
综上,可知 ReentrantLock 比直接使用 synchronized 更安全,就算线程在 tryLock()失败时,也担心死锁问题。
使用 Condition 对象来实现 wait() 和notify() 功能
class TaskQueue {
private final Lock lock = new ReentrantLock(); // 定义 ReentrantLock对象
private final Condition condition = lock.newCondition(); // 由 ReentrantLock对象获取到 Condition
private Queue<String> queue = new LinkedList<>(); // 定义队列
public void addTask(String s) {
lock.lock(); // 获取锁对象
try {
queue.add(s); //元素入队
condition.signalAll(); // condition对象调用 signalAll() 类似于 notifyAll()
} finally {
lock.unlock(); // 释放锁对象
}
}
public String getTask() {
lock.lock(); // 获取锁对象
try {
while (queue.isEmpty()) {
condition.await(); // 调用 await() 等待
}
return queue.remove();
} finally {
lock.unlock(); // 释放锁对象
}
}
}
Condition 对象必须从 Lock 实例的 newCondition() 方法返回,才能获得绑定 Lock 实例的 Condition 实例。
Condition 提供的 await()、signal()、signalAll() 原理和 synchronized 锁对象的 wait()、notify()、notifyAll() 是一致的
- await()会释放当前锁,进入等待状态;
- signalAll() 会唤醒所有等待的线程;
- signal() 会唤醒某个等待线程
与上面 tryLock()类似,condition.await() 可以在等待指定时间后,还没被其他线程通过signal()或signalAll()唤醒,即可自己醒来。
if (condition.await(1, TimeUnit.SECOND)) {
// 被其他线程唤醒后 处理逻辑
} else {
// 指定时间内没有被其他线程唤醒 处理逻辑
}
综上,使用 Condition 配合 Lock,可以实现更灵活的线程同步。
总结几点💫
- ReentrantLock 可以替代 synchronized 进行同步处理机制
- 使用 ReentrantLock 获取锁更安全
- 先获取到锁,再执行try {...}代码块,最后在finally块释放锁对象
- ReentrantLock 可使用tryLock()尝试获取锁
- Condition 可替代已经过时的 wait()和notify()
- Condition 对象必须从 Lock 对象获取
在掘金(JUEJIN)一起进步,一起成长!