为什么要线程通信
1、多个线程并发执行时,在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
2、当然如果我们没有使用线程通信来使用多线程共同操作同一份数据的话,虽然可以实现,但是在很大程度会造成多线程之间对同一共享变量的争夺,那样的话势必会造成很多错误和损失。
3、所以,我们才引出了线程之间的通信,多线程之间的通信能够避免对同一共享变量的争夺。
生产者与消费者
生产者与消费者是个很好的线程通信的例子,生产者在一个循环中不断生产共享数据,而消费者则不断消费生产者生产的共享数据。程序必须保证有共享数据,如果没有,消费者必须等待生产新的共享数据。两者之间的数据关系如下:
1) 生产者生产前,如果共享数据没有被消费,则生产等待;生产者生产后,通知消费者消费。
2)消费者消费前,如果共享数据已经被消费完,则消费者等待;消费者消费后,通知生产者生产。
wait 使线程停止运行,notify 使停止的线程继续运行:
在调用 notify()之前,线程必须获得该对象的对象级别锁;
执行完 notify()方法后,不会马上释放锁,要直到退出 synchronized 代码块,当前线程才会释放锁。
notify()一次只随机通知一个线程进行唤醒
在调用 wait()之前,线程必须获得该对象的对象级别锁;
执行 wait()方法后,当前线程立即释放锁;
从 wait()返回前,线程与其他线程竞争重新获得锁
当线程呈 wait()状态时,调用线程的 interrup()方法会出现 InterrupedException 异常
wait(long)
是等待某一时间内是否有线程对锁进行唤醒,超时则自动唤醒。wait()
:将当前执行代码的线程进行等待,置入”预执行队列”。notify()
:通知可能等待该对象的对象锁的其他线程。随机挑选一个呈wait状态的线程,使它等待获取该对象的对象锁。notifyAll()
和notify()
差不多,只不过是使所有正在等待队中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。
为了解决生产者和消费者的矛盾,引入了等待/通知(wait/notify)机制。
class Producer extends Thread {
Queue q;
Producer(Queue q) {
this.q = q;
}
public void run() {
for (int i = 1; i < 5; i++) {
q.put(i);
}
}
}
class Consumer extends Thread {
Queue q; // 声明队列q
Consumer(Queue q){
this.q = q; // 队列q初始化
}
public void run() {
while (true) {// 循环消费元素
q.get(); // 获取队列中的元素
}
}
}
Producer 是一个生产者类,该生产者类提供一个以共享队列作为参数的构造方法,它的run 方法循环产生新的元素,并将元素添加于共享队列;Consumer 是一个消费者类,该消费者类提供一个以共享队列作为参数的构造方法,它的 run 方法循环消费元素,并将元素从共享队列删除。
共享队列
共享队列类是用于保存生产者生产、消费者消费的共享数据。共享队列有两个域:value(元素的数目)、isEmpty(队列的状态)。共享队列提供了put和 get 两个方法。
class Queue {
int value = 0; // 声明,并初始化整数类型数据域value
boolean isEmpty = true; // 声明,并初始化布尔类型数据域isEmpty,用于判断队列的状态
// 生产者生产方法
public synchronized void put(int v) {
// 如果共享数据没有被消费,则生产者等待
if (!isEmpty) {
try {
System.out.println("生产者等待");
wait(); // 进入等待状态
} catch (Exception e) // 捕获异常
{
e.printStackTrace(); // 异常信息输出
}
}
value += v; // value值加v
isEmpty = false; // isEmpty赋值为false
System.out.println("生产者共生产数量:" + v);
notify();
}
public synchronized int get() {
if (isEmpty) {
try {
System.out.println("消费者等待");
wait();
} catch (Exception e) {
e.printStackTrace();
}
}
value--;
if (value < 1) {
isEmpty = true;
}
System.out.println("消费者消费一个,剩余:" + value);
notify();
return value;
}
}
生产者调用put方法生产共享数据,如果共享数据不为空,生产者线程进入等待状态;否则将生成新的数据,然后调用notify方法唤醒消费者线程进行消费;消费者调用get方法消费共享数据,如果共享数据为空,消费者进入等待状态,否则将消费共享数据,然后提调用notify方法唤醒生产者线程进行生产。
运行生产者与消费者
下面是生产者与消费者程序的主程序。
public class ThreadCommunication {
public static void main(String[] args) {
Queue q = new Queue();
Producer p = new Producer(q);
Consumer c = new Consumer(q);
c.start();
p.start();
}
}
注意:考虑到程序的安全性,多数情况下使用 notifiAll(),除非明确可以知道唤醒哪一个线程。wait方法调用的前提条件是当前线程获取了这个对象的锁,也就是说 wait方法必须放在同步块或同步方法中。