一、等待通知机制wait()与notify()

  在线程中除了线程同步机制外,还有一个最重要的机制就是线程之间的协调任务。比如说最常见的生产者与消费者模式,很明显如果要实现这个模式,我们需要创建两个线程,一个生产者,一个消费者;有两个线程还不够,如果当生产者生产商品完成后,消费者如何知道要去消费生产的商品?为此JDK给我们可提供了wait()和notify()方法来进行线程之间的通信,从而解决了生产者完成后通知消费者进行消费的问题。

  wait()和notify()是等待/唤醒机制,调用wait()方法可以让当前线程阻塞,只有当使用notify()时,才能让阻塞的线程继续执行。这样的机制就能够达道两个线程之间通信的功能。

  我们来看看这两个方法:

  1.wait()、notify()和notifyAll()方法是都是Objec类中自带的方法,因此所有的类都能够使用;

  2.wait()、notify()和notifyAll()是native方法,无法被重写;

  3.调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的锁;

  4.notify()方法能够唤醒一个正在等待的线程,如果有多个线程都在等待被唤醒,调用notify()方法只能随机唤醒一个方法;

  5.调用notifyAll()方法能够唤醒所有正在等待的线程;

  6.使用wait()、notify()和notifyAll()方法必须给线程加锁。

  

  我们用一个例子来介绍生成者与消费者模式:

  生产者代码:

/**
* 生产者,生成了10个商品后通知消费者消费
*/
public class Productor implements Runnable { @Override
public void run() {
//注意,这里生成者和消费者必须拿同一把锁,如果锁不相同,则这个机制无法成功使用
synchronized (Job.value) {
int i = 0;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (i < 10) {
//生产任务
Job.value[i] = "商品:" + i;
System.out.println("开始生产" + Job.value[i]);
i++;
}
//生产完成后,唤醒所有线程,让消费者消费
System.out.println("生产完成,开始消费");
Job.value.notifyAll();
}
}
}

  消费者代码:

/**
* 消费者,等待生产者生成完商品后,才开始消费
*/
public class Consume implements Runnable { @Override
public void run() {
//注意,这里生产者和消费者必须拿同一把锁,如果锁不相同,则这个机制无法成功使用
synchronized (Job.value) {
//先判断有没商品生产,如果没有则开始等到生产者生成
if (Job.value[0] == null) {
try {
//等待生产
System.out.println("当前没有任务,等到生产");
Job.value.wait(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产完成后,唤醒消费者后面的线程任务,消费商品
for (String string : Job.value) {
System.out.println("消费"+string);
}
} }
}

  测试类:

public class Job {

    public static String value[]=new String[10] ;

    public static void main(String[] args) {

        Productor productor=new Productor();

        Consume consume=new Consume();

        new Thread(consume).start();
new Thread(productor).start(); }
}

  运行的结果:

二、ReentrantLock中的等待唤醒机制Condition类

  前面我们已经介绍了JDK中object自带的wait()与notify()方法,但是notify()在多线程环境下只能随机唤醒一个线程,这样的功能实在是有些鸡肋,为此在AQS(AbstractQueuedSynchronizer)中,为我们提供了一个Condition类实现了等待唤醒机制,并且Condition中唤醒的机制更加灵活。

  Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition。

  Condition中常用的是await()和signal()方法,与wait()与notify一样,必须获取同一把锁才能实现等待唤醒机制,接下来我们就使用Condition中await()和signal()方法对我们的生产者和消费者模式进行改造:再增加一个消费者2,如果商品队列为空,两个消费者都进行等待,当生产者生产完成后唤醒消费者1,消费者1消费完成后唤醒消费者2,这样就能够指定唤醒一个线程

  改造后的生成者:

/**
* 生产者,生成了10个商品后唤醒所有线程
*/
public class Productor implements Runnable {
//获取的锁
Lock lock;
//生成者和消费者1使用的等待/通知对象,用于绑定生成者和消费者1的等待/通知关系
Condition condition; public Productor(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
} @Override
public void run() {
//注意,这里生成者和消费者必须拿同一把锁,如果锁不相同,则这个机制无法成功使用
lock.lock();
int i = 0;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (i < 5) {
//生产任务
Job.value[i] = "商品:" + i;
System.out.println("开始生产" + Job.value[i]);
i++;
}
//生产完成后,让消费者1消费
System.out.println("生产完成,开始消费");
condition.signal();
//必须要释放锁
lock.unlock();
}
}

  改造后的消费者1:

/**
* 消费者1,等待生产者生成完商品后,才开始消费
*/
public class Consume implements Runnable {
//获取的锁
Lock lock;
//生成者和消费者1使用的等待/通知对象,用于绑定生成者和消费者1的等待/通知关系
Condition condition;
//消费者1和消费者2使用的等待/通知对象,用于绑定消费者1和消费者2的等待/通知关系
Condition consumeCondition; public Consume(Lock lock, Condition condition, Condition consumeCondition) {
this.lock = lock;
this.condition = condition;
this.consumeCondition = consumeCondition;
} @Override
public void run() {
//注意,这里生产者和消费者必须拿同一把锁,如果锁不相同,则这个机制无法成功使用
lock.lock();
//先判断有没商品生产,如果没有则开始等到生产者生成
if (Job.value[0] == null) {
try {
//等待生产
System.out.println("我是消费者1当前没有任务,等待生产");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产完成后,唤醒消费者后面的线程任务,消费商品
for (String string : Job.value) {
System.out.println("消费者1清理"+string);
}
System.out.println("消费者1清理完毕,唤醒消费者2进行消费");
//当所有都消费完成后,通知消费者2进行消费
consumeCondition.signal();
//必须要释放锁
lock.unlock(); }
}

  新增消费者2:

/**
* 消费者2,等待消费者1清理完成商品后,才开始消费
*/
public class Consume2 implements Runnable {
//获取的锁
Lock lock;
//消费者1和消费者2使用的等待/通知对象,用于绑定消费者1和消费者2的等待/通知关系
Condition consumeCondition; public Consume2(Lock lock, Condition consumeCondition) {
this.lock = lock;
this.consumeCondition = consumeCondition;
} @Override
public void run() {
//注意,这里生产者和消费者必须拿同一把锁,如果锁不相同,则这个机制无法成功使用
lock.lock();
//先判断有没商品生产,如果没有则开始等到生产者生成
if (Job.value[0] == null) {
try {
//等待生产
System.out.println("我是消费者2,当前没有任务,等待生产");
consumeCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产完成后,唤醒消费者后面的线程任务,消费商品
for (String string : Job.value) {
System.out.println("消费者2消费"+string);
}
//必须要释放锁
lock.unlock(); }
}

  测试类:

public class Job {

    //创建一把锁
private static Lock lock = new ReentrantLock();
//生成者和消费者1使用的等待/通知对象,用于绑定生成者和消费者1的等待/通知关系
private static Condition condition = lock.newCondition();
//消费者1和消费者2使用的等待/通知对象,用于绑定消费者1和消费者2的等待/通知关系
private static Condition consumeCondition = lock.newCondition(); public static String value[] = new String[5]; public static void main(String[] args) { Productor productor = new Productor(lock,condition); Consume consume = new Consume(lock,condition,consumeCondition); Consume2 consume2=new Consume2(lock,consumeCondition);
new Thread(consume2).start();
new Thread(consume).start();
new Thread(productor).start(); }
}

运行的结果:

java并发学习--第六章 线程之间的通信的更多相关文章

  1. java并发学习--第三章 线程安全问题

    线程的安全问题一直是我们在开发过程中重要关注的地方,出现线程安全问题的必须满足两个条件:存在着两个或者两个以上的线程:多个线程共享着共同的一个资源, 而且操作资源的代码有多句.接下来我们来根据JDK自 ...

  2. java并发学习第五章--线程中的锁

    一.公平锁与非公平锁 线程所谓的公平,就是指的是线程是否按照锁的申请顺序来获取锁,如果是遵守顺序来获取,这就是个公平锁,反之为非公平锁. 非公平锁的优点在于吞吐量大,但是由于其不是遵循申请锁的顺序来获 ...

  3. Java并发学习之中的一个——线程的创建

    本文是学习网络上的文章时的总结,感谢大家无私的分享. 1.与每一个Java语言中的元素一样,线程是对象.在Java中,我们有两种方式创建线程: a.通过直接继承thread类,然后覆盖run方法. b ...

  4. java之等待唤醒机制(线程之间的通信)

    线程间通信 概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同.比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消 ...

  5. java并发学习--第四章 JDK提供的线程原子性操作工具类

    在了解JDK提供的线程原子性操作工具类之前,我们应该先知道什么是原子性:在多线程并发的条件下,对于变量的操作是线程安全的,不会受到其他线程的干扰.接下来我们就学习JDK中线程的原子性操作. 一.CAS ...

  6. java并发学习--第七章 JDK提供的线程工具类

    一.ThreadLocal ThreadLocal类用于隔离多线程中使用的对象,为ThreadLocal类中传递的泛型就是要隔离的对象,简单的来说:如果我们在主线程创建了一个对象,并且需要给下面的多线 ...

  7. Java并发学习之十九——线程同步工具之Phaser

    本文是学习网络上的文章时的总结.感谢大家无私的分享. JDK 1.7 加入了一个新的工具Phaser.Phaser的在功能上与CountDownLatch有部分重合. 以下使用Phaser类来同步3个 ...

  8. JAVA多线程学习八-多个线程之间共享数据的方式

    多个线程访问共享对象和数据的方式 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做. 如果每个线程执行的代码不同,这 ...

  9. java并发编程(十一)线程间的通信notify通知的遗漏

    notify通知的遗漏很容易理解,即threadA还没开始wait的时候,threadB已经notify了,这样,threadB通知是没有任何响应的,当threadB退出synchronized代码块 ...

随机推荐

  1. kibana的安装和监控

    1.1:kibana搭建 kibana只需要在一台机器安装即可 1):解压 tar -zxvf kibana-5.5.2-linux-x86_64.tar.gz -C /home/angel/serv ...

  2. 手机H5设计尺寸

    手机型号 导航栏和状态栏的高度 宽度 高度 可视区域高度 iPhone 4 (4, 4S) 64px 320px 480px 416px iPhone 5 (5c, 5s) 64px 320px 56 ...

  3. 【洛谷P5018 对称二叉树】

    话说这图也太大了吧 这题十分的简单,我们可以用两个指针指向左右两个对称的东西,然后比较就行了 复杂度O(n*logn) #include<bits/stdc++.h> using name ...

  4. Zipf's law

    w https://www.bing.com/knows/search?q=马太效应&mkt=zh-cn&FORM=BKACAI 马太效应(Matthew Effect),指强者愈强. ...

  5. iOS即时通讯之CocoaAsyncSocket源码解析二

    原文 前言 本文承接上文:iOS即时通讯之CocoaAsyncSocket源码解析一 上文我们提到了GCDAsyncSocket的初始化,以及最终connect之前的准备工作,包括一些错误检查:本机地 ...

  6. winform最小化及添加右键

    private void PrintService_SizeChanged(object sender, EventArgs e) { if (this.WindowState == FormWind ...

  7. sorted排序为什么不是我想要的结果?

    数据源: a=['7465', '7514', '8053', '8267', '8507', '8782', '9091', '9292', '9917', '10000', '10009'] 我以 ...

  8. 40 insert语句的锁

    40 insert语句的锁 上一篇文章中对mysql自增主键锁做了优化,尽量在申请到自增id后,就释放自增锁. 因此,insert语句是一个很轻量的操作,不过,这个结论对于”普通的insert”才生效 ...

  9. 前端 CSS 优先级 样式设置important

    !important 的使用. !important方式来强制让样式生效,但并不推荐使用.因为如果过多的使用!important会使样式文件混乱不易维护. 万不得已可以使用!important 现在选 ...

  10. MD5加密 和 自定义加密解密

    public class EncryptString { /// <summary> /// MD5加密 /// </summary> /// <param name=& ...