之前对Java多线程中的wait/notify机制理解都不是很清晰,最近看了一本技术书,通过里面的讲解再配上一些博客,终于算是对wait/notify有了进一步的理解。

下面就来说说我对这两个方法的认识:

都知道在Java中,我们可以通过继承Thread或者实现Runnable接口来实现多线程,这些线程会各自执行自己的任务,但是一个人的力量是有限的,一个线程的力量也是有限的,要想使系统各部分配合得更好,我们需要实现各个线程间的通信。要实现线程间的通信最好的方法就是使用wait/notify机制。

这里有两个新问题:

  1. 什么是线程间的通信?
  2. 如何使用wait/notify机制实现线程间的通信?

为了解决这两个问题,我们来看看下面这段代码:

public class ThreadA extends Thread{
private MyList list;
public ThreadA(MyList list) {
this.list = list;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
list.add();
System.out.println("第 "+i+" 此执行add操作");
}
}
public static void main(String[] args) {
MyList list = new MyList();
ThreadA a = new ThreadA(list);
ThreadB b = new ThreadB(list);
a.start();
b.start();
}
} class ThreadB extends Thread{
private MyList list;
public ThreadB(MyList list) {
this.list = list;
}
@Override
public void run() { while( true ) {
if (list.size() == 3) {
System.out.println("线程Thread检测到list中的长度变为3了,停止该线程");
Thread.interrupted();
}
}
}
}
class MyList{
private ArrayList<String> list = new ArrayList<String>();
public void add() {
list.add("abc");
}
public int size() {
return list.size();
}
}

执行结果如下:

第 0 此执行add操作

第 1 此执行add操作

第 2 此执行add操作

线程Thread检测到list中的长度变为3了,停止该线程

第 3 此执行add操作

第 4 此执行add操作

在上面的例子中实现了两个线程,其中线程ThreadA的任务是向list中添加值,而线程ThreadB的任务是监听list中的数量,一旦list的长度达到了3,就通过interrupted方法停止该线程的监听操作。

在这里线程ThreadA与线程ThreadB就实现了通信,这个例子中的通信指的就是:线程ThreadA执行向list添加值的操作,而线程ThreadB通过不断的执行while监听来观察线程ThreadA的操作,从而做出自己相应的操作。可是这种通信方式有一个弊端,线程ThreadB需要不断的执行while循环来达到监听的目的,这样对CPU的资源会产生浪费。服务端的资源是非常宝贵的,所以就出现了wait/notify机制来解决这个问题。

我们看看使用wait/notify机制后,上面的线程间通信会变成什么样?


public class ThreadA extends Thread{
private MyList list;
public ThreadA(MyList list) {
this.list = list;
}
@Override
public void run() {
synchronized (list) {
for (int i = 0; i < 5; i++) {
list.add();
System.out.println("第 "+i+" 此执行add操作");
try {
if (list.size() == 3)
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
MyList list = new MyList();
ThreadA a = new ThreadA(list);
ThreadB b = new ThreadB(list);
a.start();
b.start();
}
}
class ThreadB extends Thread{
private MyList list;
public ThreadB(MyList list) {
this.list = list;
}
@Override
public void run() {
synchronized (list) {
System.out.println("线程Thread检测到list中的长度变为3了,停止该线程");
list.notify();
System.out.println("notify方法调用结束");
}
}
}
class MyList{
private ArrayList<String> list = new ArrayList<String>();
public void add() {
list.add("abc");
}
public int size() {
return list.size();
}
}

执行结果如下:

第 0 此执行add操作

第 1 此执行add操作

第 2 此执行add操作

线程Thread检测到list中的长度变为3了,停止该线程

notify方法调用结束

第 3 此执行add操作

第 4 此执行add操作

上面是使用了wait/notify机制后的代码实现,两种方式的执行结果相同。但是代码的实现方式却大有不同,可以看到线程ThreadB里没有了while循环,说明在该线程里不用一直监听list中的值了,所以资源浪费的问题得到了解决,这其中的原理是什么呢?

首先需要注意的一点是,wait/notify方法必须存在于同步代码块里,因为这两个方法需要由锁对象去调用,在上面的例子中锁对象是list,调用wait与notify的方法都是同一个锁对象,具体用意在下面再讲。

然后我们来仔细说说代码的执行流程,首先我们先启动的是a线程,a线程拿到锁对象list,进入该线程run方法中的同步代码块,执行for循环,for循环中一直执行add添加操作,并进行if判断,当执行到第三次的时候进入if判断,执行list.wait(),当执行完这一句代码,当前线程立即释放list锁对象,由于此时,b线程也在等待同一把锁对象,所以该线程的拿到锁对象后执行对应的同步代码块内的内容。

当执行完list.notify()后,线程b会唤醒线程a,使其处于runnable状态,一旦抢到锁对象后立即接着list.wait()后的代码执行,与list.wait()不同的是,线程b在执行完list.notify()后没有立即释放锁,而是在执行完同步代码块中的所有内容后,锁才会被释放,锁被释放后,由于线程a已经被notify唤醒,所以会接着上次的地方执行。


在使用等待/唤醒机制的时候,有几个需要注意的地方。

  • 一个notify()方法只会唤醒一个处于等待中的线程,要想唤醒所有,可以使用notifyAll()方法
  • notify()方法只会唤醒被同一个锁对象wait()的线程。

对于第二点,如果我们将ThreadB修改成这样:

class ThreadB extends Thread{
private MyList list;
public ThreadB(MyList list) {
this.list = list;
}
@Override
public void run() {
synchronized (this) {
System.out.println("线程Thread检测到list中的长度变为3了,停止该线程");
this.notify();
System.out.println("notify方法调用结束");
}
}
}

执行结果如下,并且程序一直没有停止,可见线程a没有被唤醒。

第 0 此执行add操作

第 1 此执行add操作

第 2 此执行add操作

线程Thread检测到list中的长度变为3了,停止该线程

notify方法调用结束

这是因为调用notify与wait方法不是使用的同一个锁对象,可以理解为这两个notify和wait不是一对,所以没达到唤醒的目的。


总结

经过对比发现,我发现wait/notify机制更像是一种主动推送的实现,一旦A线程达到了某种状态,可以通过wait方法来通知与A线程使用同一个锁对象的线程,让他们去做相应的操作,由于只有一把锁,所以只能有一个线程获得执行机会,这个获得执行机会的线程肯定是在A线程中达到某种状态后才会执行,所以也就间接实现了通信的目的。

使用wait/notify实现线程间的通信的更多相关文章

  1. wait/notify实现线程间的通信

    使线程之间进行通信之后,系统间的交互性更加强大,在大大提高CPU利用率的同时还会使程序对各线程任务在处理的过程中进行有效的把控与监督. 1.不使用wait/notify实现线程间通信 使用sleep( ...

  2. Java核心知识点学习----多线程并发之线程间的通信,notify,wait

    1.需求: 子线程循环10次,主线程循环100次,这样间隔循环50次. 2.实现: package com.amos.concurrent; /** * @ClassName: ThreadSynch ...

  3. java多线程详解(6)-线程间的通信wait及notify方法

    Java多线程间的通信 本文提纲 一. 线程的几种状态 二. 线程间的相互作用 三.实例代码分析 一. 线程的几种状态 线程有四种状态,任何一个线程肯定处于这四种状态中的一种:(1). 产生(New) ...

  4. Java 多线程(七) 线程间的通信——wait及notify方法

    线程间的相互作用 线程间的相互作用:线程之间需要一些协调通信,来共同完成一件任务. Object类中相关的方法有两个notify方法和三个wait方法: http://docs.oracle.com/ ...

  5. Java-JUC(九):使用Lock替换synchronized,使用Condition的await,singal,singalall替换object的wait,notify,notifyall实现线程间的通信

    Condition: condition接口描述了可能会与锁有关的条件变量.这些用法上与使用object.wait访问隐式监视器类似,但提供了更强大的功能.需要特别指出的是,单个lock可能与多个Co ...

  6. 并发编程系列小结(线程安全,synchronized,脏读,线程间的通信wait/notify,线程的三种实现方式Demo,可替代wait/notify的方法)

    线程安全: 当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法就是线程安全的) synchronized: 可以在任意对象或方法上加锁,而加锁的这段代码称为 ...

  7. java线程基础巩固---线程间通信快速入门,使用wait和notify进行线程间的数据通信

    之前已经对于线程同步相关的知识点进行了详细的学习,这次来学习一下线程间的通信相关的知识,话不多说直接用代码进行演练,以一个简陋的生产者消费者模型来初步了解下线程间通信是怎么一回事. 生产消费者第一版: ...

  8. Java多线程中线程间的通信

    一.使用while方式来实现线程之间的通信 package com.ietree.multithread.sync; import java.util.ArrayList; import java.u ...

  9. 关于synchronized和lock 的使用及其在线程间的通信

    题目要求:子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次 synchronized的使用 import java.util.conc ...

随机推荐

  1. MyCat负载均衡 下篇

    MyCat负载均衡 下篇   之前在 一步一步在Windows中使用MyCat负载均衡 上篇 中已经讲了如何配置出MyCat.下面讲其相关的使用. 五.配置MyCat-eye 对于MyCat监控官网还 ...

  2. Java SpringMVC实现国际化整合案例分析(i18n) 专题

    所谓国际化就是支持多种语言,web应用在不同的浏览环境中可以显示出不同的语言,比如说汉语.英语等.下面我将以具体的实例来举例说明: (1)新建动态Javaweb项目,并导入几个SpringMVC必需的 ...

  3. 关于easyui-accordion的添加以及显示隐藏菜单的使用

    <script type="text/javascript"> $(function() { leftMenus(); }); function leftMenus() ...

  4. vs2008 命令窗口 命令窗口 和 反汇编窗口的使用

    visual studio 的功能相当强大,用了一年多,也只是了解了皮毛.今天学习了一下VS2008 的 即时窗口 命令窗口 和 反汇编窗口的使用.之所以会想到要使用即时窗口是因为最近开发遇到了一个问 ...

  5. [C 语言]判断某文件是文件夹还是文件

    #include <sys/stat.h> #include <stdio.h> int _tmain(int argc, _TCHAR* argv[]){ char* fil ...

  6. ASP.NET Core 路由 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 路由 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 路由 前两章节中,我们提到 ASP.NET Core 支持 MVC 开发 ...

  7. QT 窗体控件的透明度设置(三种方法)

    整个窗体 当设置QT的窗体(QMainWindow, QDialog)时,直接用 targetForm->setWindowOpacity()   函数即可实现,效果为窗体及窗体内所有控件都透明 ...

  8. 将QuickReport报表保存为图片(使用TMetaFile和TMetafileCanvas)

    //将报表第iPageNo页存为BMP文件     procedure   ReportSaveToBMPFile(sFileName   :string;   iPageNo   :integer) ...

  9. cordova-plugin-local-notifications发送Android本地消息

    原文:cordova-plugin-local-notifications发送Android本地消息 1.GitHub源代码地址: https://github.com/katzer/cordova- ...

  10. Qt打开外部程序和文件夹需要注意的细节(注意QProcess的空格问题,以及打开本地文件时,需要QUrl::fromLocalFile才可以)

    下午写程序中遇到几个小细节,需要在这里记录一下. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 QProcess *process = new QProcess(this ...