每篇一句
浓香最无著处,渐冷香、风露成霏。
《声声慢·咏桂花》 - 宋代 - 吴文英
前言
ReentrantLock 是可重入锁,上一篇博客 可重入锁和死锁 已经介绍了什么是可重入锁,以及需要注意的点. 这里我再来写下ReentrantLock的使用, 也就是API了
先来介绍下Lock接口
从java5开始,引入了一个高级的处理并发的java.util.concurrent包, 他提供了大量更加高级的并发功能处理, 极大的简化和增强了多线程编程.
为什么有synchronized还设计lock接口?
- synchronized在一个线程获得锁对象时候,如果进入了阻塞状态,比如sleep之后,一直占用锁对象, 其他线程只能等待其执行结束后释放锁.
- 还比如在多线程进行读操作的时候,如果只有一个线程能读, 其他线程在等待,那是一种相对糟糕的现象
但是lock就能解决以上的问题,他比synchronized更加强大
synchronized和lock的不同点?
- 前者是关键字,后者是接口
- 前者不需要手动释放锁,后者需要手动调用方法释放锁
ReentrantLock实现了Lock接口.
我们来看一下传统的synchronized代码:
public class Counter {
private int count;
public void add(int n) {
synchronized(this) {
count += n;
}
}
}
如果用ReentrantLock替代,可以把代码改造为:
public class Counter {
private final Lock lock = new ReentrantLock();
private int count;
public void add(int n) {
lock.lock();
try {
count += n;
} finally {
lock.unlock();
}
}
}
因为synchronized是Java语言层面提供的语法,所以我们不需要考虑异常,而ReentrantLock是Java代码实现的锁,我们就必须先获取锁,然后在finally中正确释放锁。顾名思义,ReentrantLock是可重入锁,它和synchronized一样,一个线程可以多次获取同一个锁。
更加强大的功能: ReentrantLock可以尝试获取锁:
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
...
} finally {
lock.unlock();
}
}
上述代码在尝试获取锁的时候,最多等待1秒。如果1秒后仍未获取到锁,tryLock()返回false,程序就可以做一些额外处理,而不是无限等待下去。
所以,使用ReentrantLock比直接使用synchronized更安全,线程在tryLock()失败的时候不会导致死锁。
注意点: 必须先获取到锁,再进入try {…}代码块,最后使用finally保证释放锁;不能把获取锁的代码写到try代码块中!
Condition对象实现wait和notify的功能
使用ReentrantLock比直接使用synchronized更安全,可以替代synchronized进行线程同步。
但是,synchronized可以配合wait和notify实现线程在条件不满足时等待,条件满足时唤醒,用ReentrantLock我们怎么编写wait和notify的功能呢?
答案是使用Condition对象来实现wait和notify的功能。
class TaskQueue {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private Queue<String> queue = new LinkedList<>();
public void addTask(String s) {
lock.lock();
try {
queue.add(s);
condition.signalAll();
} finally {
lock.unlock();
}
}
public String getTask() {
lock.lock();
try {
while (queue.isEmpty()) {
condition.await();
}
return queue.remove();
} finally {
lock.unlock();
}
}
}
可见,使用Condition时,引用的Condition对象必须从Lock实例的newCondition()返回,这样才能获得一个绑定了Lock实例的Condition实例。
Condition提供的await()、signal()、signalAll()原理和synchronized锁对象的wait()、notify()、notifyAll()是一致的,并且其行为也是一样的:
-
await()会释放当前锁,进入等待状态;
-
signal()会唤醒某个等待线程;
-
signalAll()会唤醒所有等待线程;
-
唤醒线程从await()返回后需要重新获得锁。
此外,和tryLock()类似,await()可以在等待指定时间后,如果还没有被其他线程通过signal()或signalAll()唤醒,可以自己醒来:
if (condition.await(1, TimeUnit.SECOND)) {
// 被其他线程唤醒
} else {
// 指定时间内没有被其他线程唤醒
}
可见,使用Condition配合Lock,我们可以实现更灵活的线程同步。