由于Java中的多线程写起来比较长,为了使博客读起来更加简洁,我决定将java多线程部分拆分开来写。本篇是第四篇,线程间通信。
Java中线程间通信的方法
有这么几个方法,它们就定义在大家都非常熟悉的Object类中,但是大家却从来没有调用过,并且也不知道是做什么的,今天就由我带着你们熟悉一下下面的这三个方法。它们都是定义在Object类中的final方法,并且只能在synchronized上下文中调用。
- wait()方法使当前线程进入休眠,直到另一个线程进入同一个监视器并调用nofity()方法。
- nofity() 方法唤醒同一监视器内调用wait() 方法的线程,就是上面那位。
- nofityAll()方法是nofify方法的升级版,会唤醒同一监视器内调用wait()方法的所有线程,但是由于这些线程都是同步线程,所以,只有一个线程将被授权访问。
生产者与消费者的场景
如果你现在还不明白,没关系,下面我会用一个“生产者与消费者”的场景来说明。首先,我会以不正确的方式来实现生产者与消费者。出现问题后,我再用wait()方法和notify() 方法来解决问题,相信大家一看就会明白。
关于生产者与消费者
生产者与消费者是一个经典的队列问题:生产者生产只有在所有产品都消费完了以后才进行生产,消费者只有在有产品的情况下才能消费,通俗来讲就是,生产一个,消费一个,再生产一个,再消费一个。。。。。。不能连续生产或连续消费。
不正确的方式
该例子包含4个类,Queue是队列,放置生产者生产的产品,Producer和Consumer分别是生产者类和消费者类,Program类是包含main方法的类。
首先是Queue类,该类包含get()和put()这两个同步方法
public class Queue {
int n;
synchronized int get() {
System.out.println("Got:" + n);
return n;
}
synchronized void put(int n) {
this.n = n;
System.out.println("Put:" + n);
}
}
接下来分别是Producer类和Consumer类,这两个类都实现了Runnable接口并覆写了run()方法。
public class Producer implements Runnable {
Queue q;
public Producer(Queue q) {
this.q = q;
new Thread(this, "Producer").start();
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
q.put(i);
}
}
}
public class Consumer implements Runnable {
Queue q;
public Consumer(Queue q) {
this.q = q;
new Thread(this, "Consumer").start();
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
q.get();
}
}
}
下面是main()方法
public class Program {
public static void main(String[] args) {
Queue q = new Queue();
new Producer(q);
new Consumer(q);
}
}
尽管Queue类中的get()和put()方法都是同步的,但是并不能阻止生产者连续生产,也不能阻止消费者连续消费,这就导致输出的结果是错误的
Put:0
Put:1
Put:2
Put:3
Got:3
Got:3
Got:3
Got:3
Got:3
Put:4
使用wait()方法和notify()方法改造队列
接下来我们使用wait()方法和notify()方法来改造Queue类,代码如下
public class Queue {
int n;
boolean valueSet = false;
synchronized int get() {
while (!valueSet) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Got:" + n);
valueSet = false;
notify();
return n;
}
synchronized void put(int n) {
while (valueSet) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.n = n;
valueSet = true;
System.out.println("Put:" + n);
notify();
}
}
改造后的队列的get()方法能够防止连续消费,put()方法能够防止连续生产,运行程序后的输出如下:
Put:0
Got:0
Put:1
Got:1
Put:2
Got:2
Put:3
Got:3
Put:4
Got:4
大家可以好好体会一下wait()和notify()方法,以及生产者和消费者