一、线程通信
多线程各自独立运行,不可避免的,会遇到线程间互通消息的需求,即线程通信。
本篇只是多线程的入门,认识线程间通信的两个基本办法。更多的线程通信方法与这两个基本方法原理类似,并在JUC并发编程系列中介绍。
本系列第一篇中已经介绍到,线程是在进程中生成的,线程间的通信比进程间通信方便,开销也更小。线程无需另外建立线程间的连接,通过共享进程资源,即可进行通信。
线程通信基本方法:
1、管程法。即设立一个共享缓冲区存放通信数据。
2、信号灯法。类似交通灯,即设立一个共享标志位,通过标志位传递状态信息。
二、生产者消费者
下面通过分析生产者消费者经典问题,来帮助学习线程通信。
有这么一个场景,仓库中存放产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。需要满足下面条件。
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
- 如果仓库中有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。
- 当仓库满时,生产者需要立即停止生产,不能超量。
- 当仓库空时,消费者需要立即停止消费,不能产生负数。
显然,这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
synchronized关键字可以实现同步,但没办法实现不同线程之间的消息传递。
回忆前面学习到的线程方法,wait()和notifyAll()可以满足需求。
wait和notify是一个系列的方法,都是属于对象Object的方法,不是属于线程的。它们用在线程同步方法或同步块中。
-
wait():obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
-
notify():obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。
-
notifyAll():obj.notifyAll()唤醒在此对象监视器上等待的所有线程。
三、代码实现
管程法,也就是缓冲区实现线程通信:
// 生产者类
class Producer implements Runnable {
Store store;
Producer(Store store) {
this.store = store;
}
@Override
public void run() {
// 循环生产
for(int i = 1; i < 20; i++) {
store.push();
}
}
}
// 消费者类
class Consumer implements Runnable {
Store store;
Consumer(Store store) {
this.store = store;
}
@Override
public void run() {
// 循环消费
for(int i = 1; i < 20; i++) {
store.pop();
}
}
}
// 产品
class Product { }
// 仓库
class Store {
// 仓库容量
int count = 5;
// 仓库容器
List<Product> bucket = new ArrayList<>();
// 生产产品
public synchronized void push() {
// 仓库未满
if(bucket.size() < count) {
bucket.add(new Product());
System.out.println("生产者新增一个产品,仓库位置" + bucket.size());
}
// 仓库满
else {
System.out.println("仓库已满");
try {
// 唤醒其他线程
this.notifyAll();
// 生产者等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 消费产品
public synchronized void pop() {
// 仓库非空
if(bucket.size() > 0) {
System.out.println("消费者用掉一个产品,仓库位置" + bucket.size());
bucket.remove(bucket.size() - 1);
}
// 仓库已空
else {
System.out.println("仓库已空");
try {
// 唤醒其他线程
this.notifyAll();
// 消费者等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行:
public static void main(String[] args) {
// 新建仓库类
Store store = new Store();
Producer p = new Producer(store);
Consumer c= new Consumer(store);
// 启动生产者线程
new Thread(p).start();
// 启动消费者线程
new Thread(c).start();
}
输出:
类似的,信号灯法,只需要将上面示例代码中的缓冲区改成标志位即可。