线程通讯,在多线程系统中,不一样的线程执行不一样的任务;若是这些任务之间存在联系,那么执行这些任务的线程之间就必须可以通讯,共同协调完成系统任务。java
案例分析编程
在案例中明,蔬菜基地做为生产者,负责生产蔬菜,并向超市输送生产的蔬菜;消费者经过向超市购买得到蔬菜;超市怎做为生产者和消费者之间的共享资源,都会和超市有联系;蔬菜基地、共享资源、消费者之间的交互流程以下:segmentfault
在这个案例中,为何不设计成生产者直接与给消费者交互?让二者直接交换数据不是更好吗,选择先先把数据存储到共享资源中,而后消费者再从共享资源中取出数据使用,中间多了一个环节不是更麻烦了?安全
其实不是的,设计成这样是有缘由的,由于这样设计很好的体现了面向对象的低耦合的设计理念;经过这样实现的程序能更加符合人的操做理念,更加贴合现实环境;同时,也能很好的避免因生产者与消费者直接交互而致使的操做不安全的问题。网络
咱们来对高耦合和低耦合作一个对比就会很直观了:多线程
关于高耦合和低耦合的区别,电脑中主机中的集成显卡和独立显卡也是一个很是好的例子。异步
CPU
中,因此若是集成显卡出现了问题须要更换,那么会连着CPU
一块更换,其维护成本与CPU
实际上是同样的;接下来咱们使用多线程技术实现该案例,案例代码以下:ide
蔬菜基地对象,VegetableBase.java性能
// VegetableBase.java // 蔬菜基地 public class VegetableBase implements Runnable { // 超市实例 private Supermarket supermarket = null; public VegetableBase(Supermarket supermarket) { this.supermarket = supermarket; } @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) { supermarket.push("黄瓜", 1300); System.out.println("push : 黄瓜 " + 1300); } else { supermarket.push("青菜", 1400); System.out.println("push : 青菜 " + 1400); } } } }
消费者对象,Consumer.javathis
// Consumer.java // 消费者 public class Consumer implements Runnable { // 超市实例 private Supermarket supermarket = null; public Consumer(Supermarket supermarket) { this.supermarket = supermarket; } @Override public void run() { for (int i = 0; i < 100; i++) { supermarket.popup(); } } }
超市对象,Supermarket.java
// Supermarket.java // 超市 public class Supermarket { // 蔬菜名称 private String name; // 蔬菜数量 private Integer num; // 蔬菜基地想超市输送蔬菜 public void push(String name, Integer num) { this.name = name; this.num = num; } // 用户从超市中购买蔬菜 public void popup() { // 为了让效果更明显,在这里模拟网络延迟 try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.println("蔬菜:" + this.name + ", " + this.num + "颗。"); } }
运行案例,App.java
// 案例应用入口 public class App { public static void main(String[] args) { // 建立超市实例 Supermarket supermarket = new Supermarket(); // 蔬菜基地线程启动, 开始往超市输送蔬菜 new Thread(new VegetableBase(supermarket)).start(); new Thread(new VegetableBase(supermarket)).start(); // 消费者线程启动,消费者开始购买蔬菜 new Thread(new Consumer(supermarket)).start(); new Thread(new Consumer(supermarket)).start(); } }
发现了问题
运行该案例,打印出运行结果,外表一片祥和,可仍是被敏锐的发现了问题,问题以下所示:
在一片看似祥和的打印结果中,出现了一个很不祥和的特例,生产基地在输送蔬菜时,黄瓜的数量一直都是1300
颗,青菜的数量一直是1400
颗,可是在消费者消费时却出现了蔬菜名称是黄瓜的,但数量倒是青菜的数量的状况。
之因此出现这样的问题,是由于在本案例共享的资源中,多个线程共同竞争资源时没有使用同步操做,而是异步操做,今儿致使了资源分配紊乱的状况;须要注意的是,并非由于咱们在案例中使用Thread.sleep();
模拟网络延迟才致使问题出现,而是原本就存在问题,使用Thread.sleep();
只是让问题更加明显。
在本案例中须要解决的问题有两个,分别以下:
针对问题一解决方案:保证蔬菜基地在输送蔬菜的过程保持同步,中间不能被其余线程(特别是消费者线程)干扰,打乱输送操做;直至当前线程完成输送后,其余线程才能进入操做,一样的,当有线程进入操做后,其余线程只能在操做外等待。
因此,技术方案可使用同步代码块/同步方法/Lock机制来保持操做的同步性。
针对问题二的解决方案:给超市一个有无货的状态标志,
保证生产基地 ——> 共享资源 ——> 消费者
这个整个流程的完整运行。技术方案:使用线程中的等待和唤醒机制。
同步操做,分为同步代码块和同步方法两种。详情可查看个人另一篇关于多线程的文章:「JAVA」Java 线程不安全分析,同步锁和Lock机制,哪一个解决方案更好
wait
和notify
方法,wait
和notify
方法存在于Object
类中。在 java.lang.Object
类 中提供了用于操做线程通讯的方法,详情以下:
wait()
:执行该方法的线程对象会释放同步锁,而后JVM
把该线程存放到等待池中,等待着其余线程来唤醒该线程;notify()
:执行该方法的线程会唤醒在等待池中处于等待状态的的任意一个线程,把线程转到同步锁池中等待;notifyAll()
:执行该方法的线程会唤醒在等待池中处于等待状态的全部的线程,把这些线程转到同步锁池中等待;注意:上述方法只能被同步监听锁对象来调用,不然发生 IllegalMonitorStateException
。
wait和notify方法应用实例
假设 A线程
和B线程
共同操做一个X对象
(同步锁) ,A、B线程
能够经过X对象
的wait
和notify
方法来进行通讯,流程以下:
A线程
执行X对象
的同步方法时,A线程
持有X对象
的锁,B线程
没有执行机会,此时的B线程
会在X对象
的锁池中等待;A线程
在同步方法中执行X.wait()
方法时,A线程
会释放X对象
的同步锁,而后进入X对象
的等待池中;X对象
的锁池中等待锁的B线程
获取X对象
的锁,执行X
的另外一个同步方法;B线程
在同步方法中执行X.notify()
方法时,JVM
会把A线程
从X对象
的等待池中转到X对象
的同步锁池中,等待获取锁的使用权;B线程
执行完同步方法后,会释放拥有的锁,而后A线程
得到锁,继续执行同步方法;基于上述机制,咱们就可使用同步操做 + wait和notify方法来解决案例中的问题了,从新来实现共享资源——超市对象:
// 超市 public class Supermarket { // 蔬菜名称 private String name; // 蔬菜数量 private Integer num; // 超市是否为空 private Boolean isEmpty = true; // 蔬菜基地向超市输送蔬菜 public synchronized void push(String name, Integer num) { try { while (!isEmpty) { // 超市有货时,再也不输送蔬菜,而是要等待消费者获取 this.wait(); } this.name = name; this.num = num; isEmpty = false; this.notify(); // 唤醒另外一个线程 } catch(Exception e) { } } // 用户从超市中购买蔬菜 public synchronized void popup() { try { while (isEmpty) { // 超市无货时,再也不提供消费,而是要等待蔬菜基地输送 this.wait(); } // 为了让效果更明显,在这里模拟网络延迟 Thread.sleep(1000); System.out.println("蔬菜:" + this.name + ", " + this.num + "颗。"); isEmpty = true; this.notify(); // 唤醒另外一线程 } catch (Exception e) { } } }
因为wait
和notify
方法,只能被同步监听锁对象来调用,不然发生IllegalMonitorStateException
。从Java 5
开始,提供了Lock机制
,同时还有处理Lock机制
的通讯控制的Condition接口
。Lock机制
没有同步锁的概念,也就没有自动获取锁和自动释放锁的这样的操做了。
由于没有同步锁,因此Lock机制
中的线程通讯就不能调用wait
和notify
方法了;一样的,Java 5
中也提供了解决方案,所以从Java 5
开始,能够:
Lock机制
取代synchronized
代码块和synchronized
方法;Condition接口
对象的await、signal、signalAll
方法取代Object
类中的wait、notify、notifyAll
方法;Lock和Condition接口
的性能也比同步操做要高不少,因此这种方式也是咱们推荐使用的方式。
咱们可使用Lock机制和Condition接口
方法来解决案例中的问题,从新来实现的共享资源——超市对象,代码以下:
// 超市 public class Supermarket { // 蔬菜名称 private String name; // 蔬菜数量 private Integer num; // 超市是否为空 private Boolean isEmpty = true; // lock private final Lock lock = new ReentrantLock(); // Condition private Condition condition = lock.newCondition(); // 蔬菜基地向超市输送蔬菜 public synchronized void push(String name, Integer num) { lock.lock(); // 获取锁 try { while (!isEmpty) { // 超市有货时,再也不输送蔬菜,而是要等待消费者获取 condition.await(); } this.name = name; this.num = num; isEmpty = false; condition.signalAll(); } catch(Exception e) { } finally { lock.unlock(); // 释放锁 } } // 用户从超市中购买蔬菜 public synchronized void popup() { lock.lock(); try { while (isEmpty) { // 超市无货时,再也不提供消费,而是要等待蔬菜基地输送 condition.await(); } // 为了让效果更明显,在这里模拟网络延迟 Thread.sleep(1000); System.out.println("蔬菜:" + this.name + ", " + this.num + "颗。"); isEmpty = true; condition.signalAll(); } catch (Exception e) { } finally { lock.unlock(); } } }
完结,老夫虽不正经,但老夫一身的才华!关注我,获取更多编程科技知识。