多线程各自独立运行,不可避免的,会遇到线程间互通消息的需求,即线程通信。
本篇只是多线程的入门,认识线程间通信的两个基本办法。更多的线程通信方法与这两个基本方法原理类似,并在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; // 仓库容器 Listbucket = 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(); }
输出:
类似的,信号灯法,只需要将上面示例代码中的缓冲区改成标志位即可。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)