Java多线程[4]:线程间通信

时间:2022-02-16 15:08:50

由于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()方法,以及生产者和消费者