1. 线程协调
前面说到可以使用synchronized
关键字来解决线程同步(资源竞争)的问题。
但有时线程间还需要协调
运行,比如一个线程负责将接收到的任务送入队列,一个线程负责取出队列中的任务,如下:
class TaskQueue {
private final Queue<String> taskQueue = new LinkedList<>();
public synchronized void addTask(String task) {
taskQueue.add(task);
}
public synchronized String getTask() {
while (taskQueue.isEmpty()) {
// 如果队列为空,则一直等待
}
return taskQueue.remove();
}
}
上述代码存在一个问题。
初始时,线程A调用getTask()
,会因为队列为空一直等待,但此时已经对this
对象进行了加锁,其他线程无法调用addTask()
方法,那么线程A会一直等待,其他线程也无法添加新的任务至队列中。
我们可以使用wait()
和notify()
\notifyAll()
函数来解决线程间协调的问题。
class TaskQueue {
private final Queue<String> taskQueue = new LinkedList<>();
public synchronized void addTask(String task) {
taskQueue.add(task);
this.notifyAll();
}
public synchronized String getTask() {
while (taskQueue.isEmpty()) {
this.wait();
}
return taskQueue.remove();
}
}
上述代码中,调用wait()
会产生阻塞,同时释放该对象的锁,当其他线程对该对象调用notify()
\notifyAll()
后,wait()
被唤醒。
注意,此处应为while (taskQueue.isEmpty())
,若队列为空,应继续调用wait()
进行等待唤醒。
2. ReentrantLock
ReentrantLock
可以用于替代synchronized
加锁,将上述任务队列代码转为ReentrantLock
的方式:
class TaskQueue {
private final Lock lock = new ReentrantLock();
private final Queue<String> taskQueue = new LinkedList<>();
public void addTask(String task) {
lock.lock();
try {
taskQueue.add(task);
} finally {
lock.unlock();
}
}
public String getTask() {
lock.lock();
try {
while (taskQueue.isEmpty()) {
// 如果队列为空,则一直等待
}
return taskQueue.remove();
} finally {
lock.unlock();
}
}
}
与synchronized
不同的是,ReentrantLock
可以尝试获取锁:
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
...
} finally {
lock.unlock();
}
}
上述代码在尝试获取锁的时候,最多等待1秒,如果1秒后还没有获得该锁,tryLock()
将返回false
。
3. Condition
Lock
只解决了线程间同步的问题,还需要结合Condition
来解决线程间协调的问题。
Lock
与Condition
的关系类似于synchronized
与wait()/notify()/notifyAll()
的关系。
class TaskQueue {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private final Queue<String> taskQueue = new LinkedList<>();
public void addTask(String task) {
lock.lock();
try {
taskQueue.add(task);
condition.signalAll(); // 对应于this.notifyAll(),将所有线程的await唤醒。
} finally {
lock.unlock();
}
}
public String getTask() {
lock.lock();
try {
while (taskQueue.isEmpty()) {
condition.await(); // 对应于this.wait()
}
return taskQueue.remove();
} finally {
lock.unlock();
}
}
}
Java提供了很多Lock
,例如ReentrantLock
, ReadWriteLock
, StampedLock
等。
根据不同的场合,需要选择一种合适的Lock
来尽可能地提高程序的性能,同时配合Condition
来解决线程间协调的问题。