线程间协作的两种方式:wait、notify、notifyAll和Condition

转载自  线程间协作的两种方式:wait、notify、notifyAll和Condition

在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作。比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。

今天我们就来探讨一下Java中线程协作的最常见的两种方式:利用Object.wait()、Object.notify()和使用Condition。

以下是本文目录大纲:

一.wait()、notify()和notifyAll()

二.Condition

三.生产者-消费者模型的实现

若有不正之处请多多谅解,并欢迎批评指正。 

一.wait()、notify()和notifyAll()

wait()、notify()和notifyAll()是Object类中的方法:

/*** Wakes up a single thread that is waiting on this object's* monitor. If any threads are waiting on this object, one of them* is chosen to be awakened. The choice is arbitrary and occurs at* the discretion of the implementation. A thread waits on an object's* monitor by calling one of the wait methods.*/
public final native void notify();/*** Wakes up all threads that are waiting on this object's monitor. A* thread waits on an object's monitor by calling one of the* wait methods.*/
public final native void notifyAll();/*** Causes the current thread to wait until either another thread invokes the* {@link java.lang.Object#notify()} method or the* {@link java.lang.Object#notifyAll()} method for this object, or a* specified amount of time has elapsed.* The current thread must own this object's monitor.*/
public final native void wait(long timeout) throws InterruptedException;

从这三个方法的文字描述可以知道以下几点信息:

1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。

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

3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;

4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;

有朋友可能会有疑问:为何这三个不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)?其实这个问题很简单,由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。

上面已经提到,如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);

notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。

同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。

这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。

举个简单的例子:假如有三个线程Thread1、Thread2和Thread3都在等待对象objectA的monitor,此时Thread4拥有对象objectA的monitor,当在Thread4中调用objectA.notify()方法之后,Thread1、Thread2和Thread3只有一个能被唤醒。注意,被唤醒不等于立刻就获取了objectA的monitor。假若在Thread4中调用objectA.notifyAll()方法,则Thread1、Thread2和Thread3三个线程都会被唤醒,至于哪个线程接下来能够获取到objectA的monitor就具体依赖于操作系统的调度了。

上面尤其要注意一点,一个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。

下面看一个例子就明白了:

public class TestWaitAndNotify {public static Object object = new Object();public static void main(String[] args) {Thread1 thread1 = new Thread1();Thread2 thread2 = new Thread2();thread1.start();try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}thread2.start();}static class Thread1 extends Thread{@Overridepublic void run() {synchronized (object) {try {object.wait();} catch (InterruptedException e) {}System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁");}}}static class Thread2 extends Thread{@Overridepublic void run() {synchronized (object) {object.notify();System.out.println("线程" + Thread.currentThread().getName() + "调用了object.notify()");}System.out.println("线程"+Thread.currentThread().getName()+"释放了锁");}}
}

无论运行多少次,运行结果必定是:

线程Thread-1调用了object.notify()

线程Thread-1释放了锁

线程Thread-0获取到了锁

二.Condition

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,在阻塞队列那一篇博文中就讲述到了,阻塞队列实际上是使用了Condition来模拟线程间协作。

  • Condition是个接口,基本的方法就是await()和signal()方法;

  • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition() 

  •  调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

            Conditon中的await(),对应Object的wait();

            Condition中的signal(),对应Object的notify();

            Condition中的signalAll(),对应Object的notifyAll()。

三.生产者-消费者模型的实现

1、使用Object的wait()和notify()实现:

public class WaitAndNotify {private int queueSize = 10;private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);public static void main(String[] args)  {WaitAndNotify testWaitAndNotify = new WaitAndNotify();Producer producer = testWaitAndNotify.new Producer();Consumer consumer = testWaitAndNotify.new Consumer();producer.start();consumer.start();}class Consumer extends Thread{@Overridepublic void run() {consume();}private void consume() {while(true){synchronized (queue) {while(queue.size() == 0){try {System.out.println("队列空,等待数据");queue.wait();} catch (InterruptedException e) {e.printStackTrace();queue.notify();}}queue.poll();          //每次移走队首元素queue.notify();System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");}}}}class Producer extends Thread{@Overridepublic void run() {produce();}private void produce() {while(true){synchronized (queue) {while(queue.size() == queueSize){try {System.out.println("队列满,等待有空余空间");queue.wait();} catch (InterruptedException e) {e.printStackTrace();queue.notify();}}queue.offer(1);        //每次插入一个元素queue.notify();System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));}}}}
}

2.使用Condition实现

public class ConditionTest {private int queueSize = 10;private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize);private Lock lock = new ReentrantLock();private Condition notFull = lock.newCondition();private Condition notEmpty = lock.newCondition();public static void main(String[] args)  {ConditionTest conditionTest = new ConditionTest();Producer producer = conditionTest.new Producer();Consumer consumer = conditionTest.new Consumer();producer.start();consumer.start();}class Consumer extends Thread{@Overridepublic void run() {consume();}private void consume() {while(true){lock.lock();try {while(queue.size() == 0){try {System.out.println("队列空,等待数据");notEmpty.await();} catch (InterruptedException e) {e.printStackTrace();}}queue.poll(); //每次移走队首元素notFull.signal();System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");} finally{lock.unlock();}}}}class Producer extends Thread{@Overridepublic void run() {produce();}private void produce() {while(true){lock.lock();try {while(queue.size() == queueSize){try {System.out.println("队列满,等待有空余空间");notFull.await();} catch (InterruptedException e) {e.printStackTrace();}}queue.offer(1); //每次插入一个元素notEmpty.signal();System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));} finally{lock.unlock();}}}}}

参考资料

  1. 《Java编程思想》

  2. http://blog.csdn.net/ns_code/article/details/17225469

  3. http://blog.csdn.net/ghsau/article/details/7481142


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/329168.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

格密码基础:q-ary格

目录 一. 格密码的重要性 二. 格密码基础 2.1 格点的另一种理解方式 三. q-ary格 3.1 q-ary垂直格 3.2 q-ary格 3.3 二者结合 四. 论文中的q-ary格 4.1 定理1 4.2 定理2 4.3 定理3 一. 格密码的重要性 格密码的基础是研究格点上的困难问题&#xff0c;这种格点使用…

java动脑公开课_java课堂动手动脑

实验任务一&#xff1a;阅读并运行示例PassArray.java.1)源代码&#xff1a;package demo;//PassArray.java//Passing arrays and individual array elements to methodspublic class PassArray {public static void main(String[] args) {int a[] { 1, 2, 3, 4, 5 };String o…

dotnet core开发体验之开始MVC

开始 在上一篇文章&#xff1a;dotnet core多平台开发体验 &#xff0c;体验了一把dotnet core 之后&#xff0c;现在想对之前做的例子进行改造&#xff0c;想看看加上mvc框架是一种什么样的体验&#xff0c;于是我就要开始诞生今天的这篇文章来分享我的感受了。 一、项目改造加…

Java时间处理第三方包:Joda-Time

转载自 Java时间处理第三方包&#xff1a;Joda-TimeJoda-Time provides a quality replacement for the Java date and time classes.Joda-Time is the de facto standard date and time library for Java prior to Java SE 8. Users are now asked to migrate to java.time (…

Visual Studio Code五月版本更新

开源项目、跨平台代码编辑器Visual Studio Code刚发布了其1.2版本&#xff08;虽然说是2016年5月发布&#xff0c;但其实是在6月交付的&#xff09;。和往常一样&#xff0c;这次发布的版本中对于很大一部分功能都进行了改善&#xff0c;其中最值得关注的可能是一种全新整合的终…

关于SimpleDateFormat时间格式化线程安全问题

转载自 关于SimpleDateFormat时间格式化线程安全问题昨天推送的文章《关于创建和销毁对象》一文中&#xff0c;2.1重复利用对象这一小节所举的SimpleDateFormat格式化时间的例子是不合适的&#xff0c;因为多线程场景下&#xff0c;SimpleDateFormat存在线程安全问题。在此&am…

聊聊ASP.NET Core默认提供的这个跨平台的服务器——KestrelServer

跨平台是ASP.NET Core一个显著的特性&#xff0c;而KestrelServer是目前微软推出了唯一一个能够真正跨平台的Server。KestrelServer利用一个名为KestrelEngine的网络引擎实现对请求的监听、接收和响应。KetrelServer之所以具有跨平台的特质&#xff0c;源于KestrelEngine是在一…

pdfbox java.lang.outofmemoryerror_Apache PDFBox 1.8.11 发布,Java 的 PDF 处理类

Apache PDFBox 1.8.11 发布&#xff0c;此版本是个增量 bug 修复版本&#xff0c;包括大量 bug 修复和改进。现已提供下载&#xff1a;主要改进内容&#xff1a;Bug 修复[PDFBOX-962] - All sort of Problems when importing Xfdf files into PDFs ->damaged pdfs and NPEs[…

Java8 Striped64 和 LongAdder

转载自 Java8 Striped64 和 LongAdder 数据 STRIPING 根据维基百科的这段说明&#xff1a;In computer data storage, data striping is the technique of segmenting logically sequential data, such as a file, so that consecutive segments are stored on different phys…

Roslyn项目系统简介

发布15年后&#xff0c;Microsoft终于开始替换Visual Studio中基于COM的C#和Visual Basic项目系统。Microsoft谈及很多有必要放弃目前所用系统的原因&#xff1a; 原生且基于COM单线程并与UI线程绑定难以通过和&#xff08;不同用途的&#xff09;子类型类扩展到聚合之外与Visu…

fastdfs 集群 java_FastDFS集群部署(转载 写的比较好)

之前介绍过关于FastDFS单机部署&#xff0c;详见博文&#xff1a;FastDFSNginx(单点部署)事例下面来玩下FastDFS集群部署&#xff0c;实现高可用(HA)服务器规划&#xff1a;跟踪服务器1【主机】(Tracker Server)&#xff1a;192.100.139.121跟踪服务器2【备机】(Tracker Server…

简析.NET Core 以及与 .NET Framework的关系

至2002微软公司推出.NET平台已近15年&#xff0c;在互联网快速迭代的浪潮中&#xff0c;许多语言已被淘汰&#xff0c;同时也有更多新的语言涌现&#xff0c;但 .Net 依然坚挺的站在系统开发平台的一线阵营中&#xff0c;并且随着.NET Core 即将到来(2016年6月27日)的正式版&am…

Jdk1.8 JUC源码增量解析(2)-atomic-LongAdder和LongAccumulator

转载自 Jdk1.8 JUC源码增量解析(2)-atomic-LongAdder和LongAccumulator功能简介&#xff1a;LongAdder是jdk1.8提供的累加器&#xff0c;基于Striped64实现。它常用于状态采集、统计等场景。AtomicLong也可以用于这种场景&#xff0c;但在线程竞争激烈的情况下&#xff0c;Long…

mysql 密码hash算法_如何用hash创建一个mySQL用户(‘sha256’,$salt.$password)?

我肯定错过了什么.我想为select-only事务设置数据库用户帐户,但mysql不允许我在创建用户帐户时选择密码的哈希方法.这失败了&#xff1a;GRANT SELECT ON myDB.* TO selectuserlocalhostIDENTIFIED BY hash(sha256, salted-myfakelongrandompasswordstring);错误1064(42000)&am…

为什么微软逐步转变为开源公司

微软目前拥有自己的 BSD Unix 操作系统&#xff0c;支持 Ubuntu 作为 Windows 10 的一个子系统&#xff0c;最近又将 Xamarin 软件开发工具包开源&#xff0c;所有这些意味着微软已不再是比尔盖茨和史蒂夫鲍尔默的微软了。 我知道这很难令人相信&#xff0c;但微软确实正大步走…

Jdk1.8 JUC源码增量解析(1)-atomic-Striped64

转载自 Jdk1.8 JUC源码增量解析(1)-atomic-Striped64功能简介&#xff1a;Striped64是jdk1.8提供的用于支持如Long累加器&#xff0c;Double累加器这样机制的基础类。Striped64的设计核心思路就是通过内部的分散计算来避免竞争(比如多线程CAS操作时的竞争)。Striped64内部包含…

java内部类选择题_java内部类详解(附相关面试题)

说起内部类这个词&#xff0c;想必很多人都不陌生&#xff0c;但是又会觉得不熟悉。原因是平时编写代码时可能用到的场景不多&#xff0c;用得最多的是在有事件监听的情况下&#xff0c;并且即使用到也很少去总结内部类的用法。今天我们就来一探究竟。一.内部类基础在Java中&am…

开源,新的平台之战

近日&#xff0c;OpenDaylight项目的执行总监Neela Jacques在文章《开源的转变&#xff1a;一种新的平台战争》 中提到&#xff1a;开源已经成为软件公司业务战略的关键&#xff0c;是一种新的平台之战。 多年来&#xff0c;开源软件似乎处于技术产业的边缘。而如今&#xff0c…

java下载图片到手机相册_Unity保存图片到Android手机且更新相册

Android 保存图片到设备前言:在许多的应用或游戏中,大多都有保存图片或者截图等等的功能,这篇文档我们的目的是通过 Unity 保存图片,并且调用 Andorid 中的更新相册的原生方法.流程步骤:编写更新相册的 Android 原生接口 -> Unity 编写保存图片逻辑以及调用更新相册 Android…

如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例

转载自 如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例 wait, notify 和 notifyAll&#xff0c;这些在多线程中被经常用到的保留关键字&#xff0c;在实际开发的时候很多时候却并没有被大家重视。本文对这些关键字的使用进行了描述。 在 Java 中…