之前对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. Python logging模块无法正常输出日志

    废话少说,先上代码 File:logger.conf [formatters] keys=default [formatter_default] format=%(asctime)s - %(name ...

  2. url操作等

    取得url ?及以前: baseUrl = url.substr(0,url.indexOf('?')+1) searchParam = searchParam.slice(0, -1);//去掉最后 ...

  3. QT之圆形头像(使用PNG的Mask达到的效果)

    废话不多说!直接上代码. 我们在很多UI设计应用中,需要用到自定义形状头像,在这里,我对圆形头像的设计做简单的阐述,其它形状头像可参考本文做相应的更改即可.如下图所示为设计的圆形头像: 上代码: Se ...

  4. Android中使用ListView实现自适应表格

    GridView比ListView更容易实现自适应的表格,但是GridView每个格单元的大小固定,而ListView实现的表格可以自定义每个格单元的大小,但因此实现自适应表格也会复杂些(格单元大小不 ...

  5. VxWorks启动过程的详细解释(上)

    vxworks有三个图像: VxWorks Image有三种类型的文件 Loadable Images:由Boot-ROM引导通过网口或串口下载到RAM ROM-based Images(压缩/没有压 ...

  6. Python 函数调用性能记录

    之前用 JS 写项目的时候,项目组用的组件模式,一直感觉很不错.最近用 Python 做新项目,项目结构也延续了组件模式.一直没有对函数调用的性能作了解,今天突发奇想测试了一下,写了一些测试代码 首先 ...

  7. OpenGL(二十二) gluBuild2DMipmaps 加载Mip纹理贴图

    当纹理被用于渲染一个面积比它本身小很多的对象时,会由于纹理图像的降采样率不足而导致混叠现象,主要的表现特征是纹理图像的闪烁,出现纹理躁动.特别是在场景远近移动变换时,这种闪烁情况更为明显,严重可能会影 ...

  8. 我的Spring之旅(二):为请求加入參数

    1.前言 在上一篇我的Spring之旅(一)中,我们仅仅是利用不带參数的请求返回一个网页或一段json,在实际的B/S.C/S网络交互中,请求中须要自己定义的參数.本篇将简单地为之前的请求加入參数. ...

  9. 3DMax模型输入到WPF中运行

    原文:3DMax模型输入到WPF中运行 其实看看笔者文章之前,可以在网上搜索下将3Dmax模型输入到WPF的办法,大部分结果都是这篇文章.这篇文章呢?有点麻烦,就是我们3Dmax模型转换到Blend的 ...

  10. Windows下程序打包发布时的小技巧(使用Dependency Walker侦测不理想,改用VS自带的dumpbin则万无一失,还可查看dll导出的函数)

    Windows下开发的应用程序在发布时,需要将其依赖的一些动态链接库一起打进安装包里面去.这个时候,快速确定这个程序到底依赖哪些动态链接库变得非常重要.很久以前写过一篇关于Qt程序安装包制作的博客,里 ...