Java多线程编程-(5)-线程间通信机制的介绍与使用(温馨提示:图文较多,建议Wiff下打开)

线程间通信简介

我们知道线程是操作系统中独立的个体,但是这个单独的个体之间没有一种特殊的处理方式使之成为一个整体,线程之间没有任何交流和沟通的话,他就是一个个单独的个体,不足以形成一个强大的交互性较强的整体。

为了提高CPU的利用率和各线程之间相互协作,Java的一种实现线程间通信的机制是:wait/notify线程间通信,下边就一起学习一下这种线程间的通信机制。

不使用等待/通知机制实现线程间通信

假如,我们不使用下边需要介绍的机制,那我们如何实现两个线程之间的通信哪,下边看一段代码,实现的是两个线程向一个List里填充数据:

MyList代码:

线程A:

线程B:

测试类Test:

执行结果:

可以看出,当List集合中的数据为5个的时候线程B退出,虽然两个线程之间实现了通信,但是代码中我们的线程B是一直执行着while(true)循环的,直到长度为5才终止执行,显然这种方式是很消耗资源的。所以,就需要一种机制能避免上述的操作又能实现多个线程之间的通信,这就是接下来需要学习的“wait/notify线程间通信”

什么是等待/通知机制

道理很简单,就像我们去银行办业务,进门之后取票号,等到达的时候会广播通知我们办业务一样,这就是很实际的一个场景,我们取了票号就需要等待,等业务员轮到票号的时候就会广播通知。

Java中等待/通知机制的实现

Java中对应等待/通知的方法是wait()/notify(),这两个方法都是超类Object中的方法,如下图所示:

之所以会是超类Object中的方法,我们可以简单的理解:上几篇文章中我们知道任何对象都可以作为锁,而wait()/notify()是由锁调用的,想到这里自然可以体会到这里设计的巧妙之处。

一、wait方法

(1)方法wait()的作用是使当前执行代码的线程进行等待,该方法会将该线程放入”预执行队列“中,并且在wait()所在的代码处停止执行,直到接到通知或被中断为止。

(2)在调用wait()之前,线程必须获得该对象级别锁,这是一个很重要的地方,很多时候我们可能会忘记这一点,即只能在同步方法或同步块中调用wait()方法。

(3)还需要注意的是wait()是释放锁的,即在执行到wait()方法之后,当前线程会释放锁,当从wait()方法返回前,线程与其他线程竞争重新获得锁。

二、notify方法

(1)和wait()方法一样,notify()方法也要在同步块或同步方法中调用,即在调用前,线程也必须获得该对象的对象级别锁。

(2)该方法是用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。

(3)这里需要注意的是,执行notify方法之后,当前线程不会立即释放其拥有的该对象锁,而是执行完之后才会释放该对象锁,被通知的线程也不会立即获得对象锁,而是等待notify方法执行完之后,释放了该对象锁,才可以获得该对象锁。

(3)notifyAll()通知所有等待同一共享资源的全部线程从等待状态退出,进入可运行状态,重新竞争获得对象锁。

三、wait()/notify()方法总结

(1)wait()/notify()要集合synchronized关键字一起使用,因为他们都需要首先获取该对象的对象锁;

(2)wait方法是释放锁,notify方法是不释放锁的;

(3)线程的四种状态如下图:

wait/notify线程间通信示例代码

根据上述不使用wait/notify的代码改造如下:

MyList代码:

线程A:

线程B:

测试代码:

运行结果:

上述实例已经实现了简单的等待通知机制,并且我们也可以看到,虽然线程B在第五个元素的时候发出通知,而线程A实现线程B执行完之后才获得对象锁,这也可以说明,wait方法是释放锁的而notify方法是不释放锁的。

另一个案例:使用wait/notify模拟Queue

Queue是队列,我们需要实现的是阻塞的放入和得到数据,设计思路如下:

(1)初始化队列最大长度为5; (2)需要新加入的时候,判断是否长度为5,如果是5则等待插入; (3)需要消费元素的时候,判断是否为0,如果是0则等待消费;

实现代码如下:

执行结果:

其他注意事项

(1)wait()和notify()方法要在同步块或同步方法中调用,即在调用前,线程也必须获得该对象的对象级别锁。

(2)wait方法是释放锁,notify方法是不释放锁的;

(3)notify每次唤醒wait等待状态的线程都是随机的,且每次只唤醒一个;

(4)notifAll每次唤醒wait等待状态的线程使之重新竞争获取对象锁,优先级最高的那个线程会最先执行;

(5)当线程处于wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常;

其他知识点

(1)进程间的通信方式:

管道(pipe)、有名管道(named pipe)、信号量(semophore)、消息队列(message queue)、信号(signal)、共享内存(shared memory)、套接字(socket);

(2)线程程间的通信方式:

1、锁机制 1.1 互斥锁:提供了以排它方式阻止数据结构被并发修改的方法。 1.2 读写锁:允许多个线程同时读共享数据,而对写操作互斥。 1.3 条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。

对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

2、信号量机制:包括无名线程信号量与有名线程信号量 3、信号机制:类似于进程间的信号处理。

线程间通信的主要目的是用于线程同步,所以线程没有象进程通信中用于数据交换的通信机制。

原文发布于微信公众号 - Java后端技术(JavaITWork)

原文发表时间:2017-10-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大闲人柴毛毛

Java并发编程的艺术(十二)——线程安全

1. 什么是『线程安全』? 如果一个对象构造完成后,调用者无需额外的操作,就可以在多线程环境下随意地使用,并且不发生错误,那么这个对象就是线程安全的。 2. ...

3715
来自专栏从零开始学自动化测试

python笔记4-遍历文件夹目录os.walk()

前言 如何遍历查找出某个文件夹内所有的子文件呢?并且找出某个后缀的所有文件 一、walk功能简介 1.os.walk() 方法用于通过在目录树种游走输出在...

4855
来自专栏CodingToDie

Python学习(七):模块 优雅的封装

第7 章 模块 优雅的封装 Table of Contents Python中的模块 使用模块 定义模块 建议 模块的安装 模块搜索路径 作用域 编程是一种美德...

8694
来自专栏流柯技术学院

JMeter专题系列(五)检查点

检查点:我们对用户名和密码进行了参数化,那么怎样来判断jmeter有没有正确调用t.dat里面的文件呢。当然,我们可以从结果图表中查看。但我还是想在“登录”这个...

943
来自专栏Android相关

Android---SharedPreferences解析

SharedPreferences真正实现的类是:SharedPreferencesImpl

1703
来自专栏CSDN技术头条

一图读懂JVM架构解析

本文阐述了JVM的构成和组件,配图清晰易懂,是学习Java开发者的入门必读文章。 每个Java开发人员都知道字节码经由JRE(Java运行时环境)执行。但他们或...

2168
来自专栏Ryan Miao

java基础题目总结

有些基础题目由于工作中用的比较少但却又是不可少的,这样回答起来就会反应慢,不确定,不准确,特此开了文章记录遇到的不确定或者回答比较拗口的问题。 1.servle...

3519
来自专栏orientlu

FreeRTOS 消息队列

上面这几中方式中, 除了消息通知, 其他几种实现都是基于消息队列。消息队列作为主要的通信方式, 支持在任务间, 任务和中断间传递消息内容。 这一章介绍 Fre...

3872
来自专栏mwangblog

python操作文本文件

1674
来自专栏java达人

为什么volatile使用比synchronized少

在多线程编程中,我们最常用的是synchronized,而对volatile的使用,却相对较少。这一方面是因为volatile的使用场景限制,另一方面是vola...

2258

扫码关注云+社区

领取腾讯云代金券