多线程协调
synchronized解决了多线程竞争的问题。例如,对于一个任务管理器,多个线程同时往队列中添加任务,可以用synchronized加锁:
class TaskQueue {
Queue<String> queue = new LinkedList<>();
public synchronized void addTask(String s) {
this.queue.add(s);
}
}
但是无法解决多线程协调的问题,多线程协调问题也就是多个线程执行不同的方法的问题,如果处理不当可能造成线程长期占用无法释放,造成资源浪费、JVM进程无限等待的后果。
class TaskQueue {
Queue<String> queue = new LinkedList<>();
public synchronized void addTask(String s) {
this.queue.add(s);
}
public synchronized String getTask() {
while (queue.isEmpty()) {
}
return queue.remove();
}
}
上面的代码,执行getTask时,持有的锁是this锁,如果队列为空则一直等待,一直无法释放锁,由于addTask在等待this锁,一直无法增加任务,则该队列一直为空。因此陷入了循环等待。
解决办法
使用wait和notify(其中notifyAll更安全)
- 必须在已获得的锁对象上调用wait()方法;
- 在synchronized内部可以调用notify()或notifyAll()唤醒其他等待线程;
- 必须在已获得的锁对象上调用notify()或notifyAll()方法;
- 已唤醒的线程还需要重新获得锁后才能继续执行。
下面代码如果不加wait和notifyAll则无法正常结束
package org.meituan.javalearn.thread;
import lombok.SneakyThrows;
import java.util.LinkedList;
/**
* @projectName: codebetter
* @package: org.meituan.javalearn.thread
* @className: WaitNotifyThread
* @author: fangjiayueyuan
* @description: TODO
* @date: 2023/5/4 上午11:11
* @version: 1.0
*/
public class WaitNotifyThread {
public static void main(String[] args) {
LinkedList<Thread> threadPools = new LinkedList<Thread>();
final TaskQueue taskQueue = new TaskQueue();
for(int i=0;i<5;i++){
threadPools.add(new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
System.out.println(taskQueue.getTask());
}
}));
}
Thread addThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
taskQueue.addTask("The"+i+"th task to be closed");
}
}
});
for(Thread thread:threadPools){
thread.start();
}
addThread.start();
}
}
class TaskQueue{
LinkedList<String> queue = new LinkedList<String>();
public synchronized void addTask(String task){
queue.add(task);
this.notifyAll();
}
public synchronized String getTask() throws InterruptedException {
while (queue.isEmpty()){
this.wait();
}
return queue.remove();
}
}
上面代码的运行结果:
解决办法2
使用condition解决多线程协调问题
- 使用Condition时,引用的Condition对象必须从Lock实例的newCondition()返回,这样才能获得一个绑定了Lock实例的Condition实例。
- await()会释放当前锁,进入等待状态;
- signal()会唤醒某个等待线程;
- signalAll()会唤醒所有等待线程;
- 唤醒线程从await()返回后需要重新获得锁。
使用Condition进行多线程协调的代码如下,实现和上面的代码一样功能的任务队列。
class ConditionTaskQueue{
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
LinkedList<String> queue = new LinkedList<String>();
public void addTask(String task){
lock.lock();
try{
queue.add(task);
condition.signalAll();
}finally {
lock.unlock();
}
}
public String getTask() throws InterruptedException {
lock.lock();
try{
while (queue.isEmpty()){
condition.await();
}
return queue.remove();
}finally {
lock.unlock();
}
}
}