wait和notify
synchronized解决了多线程竞争的问题
- 我们可以在synchronized块中安全的对一个变量进行修改,但是它没有解决多线程协调的问题。
- 例如设计一个TaskQueue,预期效果:线程1通过addTask()不断往队列中添加任务,而线程2可以调用getTask()从队列中获取任务
class TaskQueue{
Queue<String> queue = new LinkedList<>();
public synchronized void addTask(String s){ //线程1不能执行,因为操作对象已经被线程2锁住了
this.queue.add(s);
}
public synchronized String getTask(){
while(queue.isEmpty()){ } //线程2执行getTask()时,如果队列为空,陷入死循环
return queue.remove();
}
}
上诉代码会出现死循环。
如果队列为空,则getTask()应该等待,直到队列中至少有一个任务再执行getTask()。
因此我们需要一种多线程协调的机制:当条件不满足时,线程进入等待状态
wait()方法的执行机制:
- 首先,wait()不是一个普通的Java方法,而是定义在Object类上面的一个native方法,即是由JVM虚拟机的C代码实现的
- 其次,必须在synchronized代码块中才能调用wait()方法,因为wait()方法调用的时候,线程会释放它获得的锁,wait()方法返回后,线程又回重新获得锁。我们只能在锁对象调用wait()方法。此处我们使用synchronized修饰方法,所以我们获得对象是this对象。因此只能在this对象上调用wait()方法
- 正是因为wait()方法会释放锁,所以其他的线程才能获得锁,并且进入addTask()方法。在addTask()方法内,我们向队列添加一个元素以后,就可以调用this.notify()来唤醒正在this对象上等待的线程,这样在wait()方法上等待的线程就可以被唤醒,然后从wait()方法返回以后继续执行。
import java.util.LinkedList;
import java.util.Queue;
class TaskQueue{
final Queue<String> queue = new LinkedList<>(); //定义一个LinkedList作为queue
public synchronized String getTask() throws InterruptedException{
System.out.println("开始执行");
while(this.queue.isEmpty()){ //判断queue是否是空,如果空,就释放对象锁,进入等待状态。
this.wait();
System.out.println("继续执行");
}
return queue.remove();//删除queue最上面的值
}
public synchronized void addTask(String name){
this.queue.add(name); //queue添加一个任务
this.notifyAll(); //唤醒所有正在等待的线程
System.out.println("唤醒线程");
}
}
class WorkerThread extends Thread {
TaskQueue taskQueue;
public WorkerThread(TaskQueue taskqueue){
this.taskQueue = taskqueue;
}
public void run(){
while(!isInterrupted()){ //不断的执行getTask,获取queue中的任务,一旦获取到,就打印hello name
String name;
try{
name = taskQueue.getTask();
}catch (InterruptedException e){
break;
}
String result = "Hello, "+name+"!";
System.out.println(result);
}
}
}
public class Main{
public static void main(String[] args) throws Exception{
TaskQueue taskqueue = new TaskQueue();
WorkerThread worker = new WorkerThread(taskqueue);
worker.start();
taskqueue.addTask("Bob");
Thread.sleep(1000);
taskqueue.addTask("Alice");
Thread.sleep(1000);
taskqueue.addTask("Tim");
Thread.sleep(1000);
worker.interrupt();
worker.join();
System.out.println("End");
}
}
为什么在while循环wait,而不是一个if语句中wait ?
如果存在2个或者更多的线程在wait(),唤醒当前线程的不一定是执行添加队列的线程1。当非添加队列的线程1唤醒获取队列的线程2时:
- 使用while可以判断当前队列是否为空,
- 使用if继续执行,如果队列为空,执行remove方法会报错
总结
- 在synchronized内部可以调用wait()是线程进入等待状态
- 必须在已获得的锁对象上调用wait()方法
- 在synchronized内部可以调用notify/notifyAll()唤醒其他等待线程
- 必须在已获得的锁对象上调用notify()/notifyAll()方法