《JAVA多线程编程核心技术》 笔记:第三章:线程间通信
1.1、使用的原因:
1.2 具体实现:wait()和notify()
1.2.1 方法wait():
1.2.2 方法notify():
1.2.3 wait()和notify()使用对比:
1.3 当interrupt方法遇到wait方法
1.4 notifyAll():唤醒所有线程
1.5 方法wait(long)
二、方法join的使用
2.1 join()方法的作用:本质是wait()
2.2 join和synchronized的区别:
2.3 join()的异常
2.4 方法join(long):(本质是wait(long))
2.5 join(long)和sleep(long)的区别
2.6 注意:方法 join(long)后面的代码提前运行会出现意外
三、通过管道进行线程间通信
3.1 字节流管道
3.2 字符流管道
3.3 实战:等待/通知值交叉备份
四、生产者/消费者模式实现:wait和notify
4.0、正常和异常说明
4.1、一生产与一消费:操作值(不会假死)
4.2、多生产和多消费:操作值(假死)
4.3、多生产和多消费:操作值(假死解决)
4.4、一生产与一消费:操作栈
4.5、一生产与多消费:操作栈(解决wait条件改变与假死:if和while循环的不同以及解决)
4.5.1 if 会出现异常的原因分析:
4.5.2 while可以解决if的异常,但会导致假死
判断条件if和while里执行wait()操作的区别:
4.5.3 假死的解决:notifyAll
4.6、多生产与一消费:操作栈
4.7、多生产与多消费:操作栈
五、类ThreadLocal的使用:
5.1 方法get()和null
5.2 ThreadLocal如何实现线程变量的隔离性
六、类InheritableThreadLocal的使用:
6.1值继承
6.2 值继承再修改
七、线程状态 + 方法+ 就绪和阻塞队列-总结
7.1 线程状态转换:
7.2 线程方法说明:
7.3 就绪队列和阻塞队列
END
一、 等待/通知机制:wait()和notify()
1.1、使用的原因:
如果没有通知等待机制,则只能让线程使用while(true)
死循环,来一直执行。不断的进行条件判断,等到符合条件便自动退出。但这样线程便一直执行(轮询),会浪费CPU资源。
由此,引入等待/通知机制(原理不过说明)。
1.2 具体实现:wait()和notify()
wait()使线程停止运行,notify()使停止的线程继续运行。
1.2.1 方法wait():
wait()方法:将当前线程置于“预执行队列”,并在wait()所在的代码行处停止执行,直到接到通知或者被中断为止。
使用注意:
调用
wait()
之前:线程必须获得该对象的对象级别锁(即只能在同步方法或同步代码块中调用wait()方法)调用
wait()
时:如没有持有适当的锁,则抛出IllegalMonitorStateException(RunTimeException的一个子类,无需try/catch)执行
wait()
之后:当前线程释放锁从
wait()
返回前(即调用notify()之后):线程与其他线程竞争重新获得锁。
1.2.2 方法notify():
方法notify():用来通知那些可能等待该对象的对象锁的其他线程。如有多个线程等待,则由线程规划器随机挑选一个呈wait状态的线程,对其发出通知notify,并使他等待获取该对象的对象锁。
使用注意:
调用notify()
之前:线程必须获得该对象的对象级别锁(即只能在同步方法或同步代码块中调用notify()
方法)
调用notify()
时:如没有持有适当的锁,则抛出IllegalMonitorStateException(RunTimeException的一个子类,无需try/catch)
执行notify()
之后:
- 当前线程不会马上释放该对象锁,呈wait状态的线程也不能马上获取该对象锁。
- 要等到执行notify()的线程将程序执行完,即退出synchronized代码块后,当前线程才会释放锁,而呈wait状态的线程才可以获取该对象锁(是可以获取,不是获取到)。
- 当第一个获得了该对象锁的wait线程运行完毕之后,它会释放掉该对象锁。此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait()状态。直到这个对象发出一个notify或notifyAll。
notify一次只能通知一个线程,而每个wait的线程都只有被noyify之后才会执行。
1.2.3 wait()和notify()使用对比:
wait() | notify() |
---|---|
调用前 | 必须获得该对象的对象级别锁 |
调用时 | 没有持有适当的锁,则抛异常 |
执行后(等待被 notify()唤醒时)+锁释放 |
当前线程立马释放锁; 线程从运行状态退出,进入阻塞状态,进入等待队列直到被再次唤醒 |
被notify()唤醒后 | 线程进入就需状态,重新尝试获取对象锁,并执行wait后续代码 |
1.3 当interrupt方法遇到wait方法
当线程wait状态时,调用线程的interrupt()方法会出现InterruptedException异常。(该异常由wait方法抛出。其实遇到sleep方法和join方法同样抛异常)
更多理解可参考:阻塞(sleep等等)区别 中断(interrupt)+ 中断的意义 - baoendemao - 博客园 https://www.cnblogs.com/baoendemao/p/3804730.html
1.4 notifyAll():唤醒所有线程
1.5 方法wait(long)
wait(long)和sleep的原理很像,到期自动唤醒,相当于到期自动执行一个notify。未到期之前也可被其他notify唤醒。
二、方法join的使用
2.1 join()方法的作用:本质是wait()
方法定义:等待线程对象销毁。(即当线程销毁之后,执行的线程继续执行)
实例解释:是所属的线程对象x正常执行run()方法中的任务,而当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码。
作用:可使线程排队运行的作用,有类似同步的运行效果(synchronized)。
2.2 join和synchronized的区别:
join | synchronized |
---|---|
区别 | 内部使用wait等待 |
2.3 join()的异常
如果一个线程z在等待另一个x的join,忽然线程z调用了interrupt,那么线程z会出现异常。
但线程x还在继续执行,因为线程x没有出现异常。
理解:join本质是wait,wait遇到interrupt会抛出异常。
2.4 方法join(long):(本质是wait(long))
join(long)的理解
说明:方法join(long)中的参数是设定等待的时间。(和sleep很像)
即使线程x需要执行很久,但是只要join(long)时间到了,线程z就会继续往下执行。
- 如果在long时间内,线程没有执行完,那么以long为准。(即使线程没有执行完,线程还是会继续执行,和当前线程无关了)
- 如果在不到long的时间内,线程就执行完了,那么就以实际时间为准。
2.5 join(long)和sleep(long)的区别
方法 join(long):内部使用wait(long)实现,所以其会释放(当前线程持有的)锁。
而sleep(long):并不会释放锁。
2.6 注意:方法 join(long)后面的代码提前运行会出现意外
这个例子说了一个问题:
- join(long)会抢到锁,然后立即释放,就是为了进入wait(long)的等待队列;
- 当long过去之后,join(long)的线程会被唤醒,继续抢锁,执行后续代码。
- 但是如果其他线程也在抢锁,那么谁会抢到就不确定了。
因为不确定,所以可能会有问题。
三、通过管道进行线程间通信
3.1 字节流管道
原理和List一样,不过对于管道输入流来说,其自带的read方法,如果读取不到数据,就会自己阻塞。无需像list那些需自行让线程wait
3.2 字符流管道
和上一个没有太多区别,只是这个是字符流,上一个是字节流。
3.3 实战:等待/通知值交叉备份
只是让两个线程交替执行而已,使用一个boolean变量作为开关进行控制,没太多需要说明。
四、生产者/消费者模式实现:wait和notify
4.0、正常和异常说明
正常模式:生产1个→消费1个→生产1个→消费1个→生产1个→消费1个;
消费异常模式:生产1个→消费1个→再消费一个(无法消费,自己阻塞。然后只能等待生产者生产后将自己唤醒)→.......→生产1个→消费1个→生产1个→消费1个;
生产异常模式:生产1个→消费1个→生产1个→再生产一个(无法生产,自己阻塞。然后只能等待消费者消费后将自己唤醒)→.......→消费1个→生产1个→消费1个;
注意:一直只有一个阻塞,所以无需担心notify被错误消费;
4.1、一生产与一消费:操作值(不会假死)
根据值进行控制判断(什么时候进入阻塞状态)
总结:
- 首先:需要两个线程,生产者线程和消费者线程,生产者线程和消费者线程都必须一直执行。
- 其次,两个线程操作同一个对象。
- 生产者和消费者对该对象的操作有不同的逻辑:
- 生产者和消费者需要一个判断逻辑(该判断逻辑对生产者就是消费者处理后的状态,对消费者就是生产者生产后的状态),符合逻辑之后才能进入自己的wait;
- 生产者往该对象set值,set之后通知消费者;
- 消费者从该对象取值并消费处理,处理后通知生产者;
- 以上-END!
4.2、多生产和多消费:操作值(假死)
假死实际不是很理解...不过知道了原因,是因为notify唤醒了不该唤醒的wait,导致notify被错误消费(消费之后应再有一个notify,错误消费之后就没有了),然后后续逻辑错误,因此假死。
4.3、多生产和多消费:操作值(假死解决)
解决上述假死:将notify换为notifyAll
4.4、一生产与一消费:操作栈
根据list的size进行控制判断(什么时候进入阻塞状态)
4.5、一生产与多消费:操作栈(解决wait条件改变与假死:if和while循环的不同以及解决)
4.5.1 if 会出现异常的原因分析:
多个消费者,都处于阻塞;
如果一个消费者消费之后,执行notify(notify是随机唤醒),而该notify被另一消费者使用,另一消费者直接往下执行(不进行while的额外一重判断),直接执行后面,导致异常。
4.5.2 while可以解决if的异常,但会导致假死
while可以解决,因为while和if不一样。while那么肯定会再一次判断,判断发现是0,然后自己阻塞(即notify被浪费)了,然后导致了假死....
判断条件if和while里执行wait()操作的区别:
当被notify时:
- 如果是if,那么直接往下执行;
- 如果是while,那么会把判断条件再执行一次,这是由while本身语法决定的。
- 执行之后,再次满足才会往下执行;
- 如果不满足,则再次wait阻塞;
4.5.3 假死的解决:notifyAll
notifyAll肯定会唤醒生产者,生产者肯定会生产一个,然后继续消费,一直循环下去,肯定不会阻塞。
4.6、多生产与一消费:操作栈
这个好像没什么问题
4.7、多生产与多消费:操作栈
这个好像也没什么问题
五、类ThreadLocal的使用:
所有线程共享同一个变量情况:public static
每个线程都有自己的共享变量:使用ThreadLocal(主要解决:每个线程绑定自己的值,可以将其理解为全局存放数据的盒子,盒子中可以存放每个线程的私有数据)
5.1 方法get()和null
get()第一次调用会返回null(看源码:因为ThreadLocal的initialValue()方法返回的就是null,即每次初始化为null),除非进行set()的操作
5.2 ThreadLocal如何实现线程变量的隔离性
ThreadLocal(public static)只有一个,但每个线程只可以放入自己的值,取值的时候只会取出来自己的值,这个好像是代码自己实现的。我操,这才是ThreadLocal的牛掰之处。
为什么会这样?可以看下ThreadLocal的get和set方法。里面每次都会获取当前线程,然后再进行后续逻辑。内部是一个 ThreadLocalMap。
六、类InheritableThreadLocal的使用:
6.1值继承
使用InheritableThreadLocal可以在子线程中取得(子线程中取的是父线程的值,自己没有相关值)父线程继承下来的值。
6.2 值继承再修改
子线程可以覆盖父线程的childValue()方法,对主线程的值进行额外处理。
注意:如果子线程取值时,主线程将InheritableThreadLocal中的值进行了修改,那么子线程取到的还是旧值。
七、线程状态 + 方法+ 就绪和阻塞队列-总结
7.1 线程状态转换:
新建之后
可运行状态(从运行状态变为可运行状态)
运行状态
阻塞状态
销毁状态
- 等待被分配资源
- 执行了sleep()方法,同时等待时间超过设定时间
- 执行了sleep()方法,同时等待时间未超过设定时间
- run方法运行结束之后
- 线程调用了阻塞式IO方法,且已经返回了结果,阻塞方法执行完毕;
- 线程调用了阻塞式IO方法,且该方法返回结果之前;
- 线程成功获取了试图同步的监视器
- 线程试图获取了一个同步监视器,但该同步的监视器被其他线程持有。
4.wait线程收到其他线程发出的notify通知
- 线程执行了wait()方法,等待某个通知。
- 被suspend方法挂起的线程调用了resume方法。
- 线程调用了suspend方法将该线程挂起。
- 调用start方法,系统为其分配资源(第一次进入可运行状态)
7.2 线程方法说明:
start() 和run()
start():
线程准备执行,具体执行由线程调度器决定
yield()和sleep()
yield():
停止当前正在执行的线程,释放当前锁,让同样优先级的正在等待的线程有机会执行(只是有机会,具体怎么执行看系统,也可能还是自己执行)
suspend()和resume()
suspend():
使当前线程阻塞,不释放对象锁,只能被resume()恢复。
wait()和notify()和notifyAll()
wait():
释放当前锁,等待被notify()通知
stop()
停止线程,强制停止,不安全。
interrupt()和interrupted()和isInterrupted()
中断线程。
调用该方法的线程的状态为将被置为"中断"状态。 并非真的立即停止。
更深的理解参考:阻塞(sleep等等)区别 中断(interrupt)+ 中断的意义 - baoendemao - 博客园 https://www.cnblogs.com/baoendemao/p/3804730.html
7.3 就绪队列和阻塞队列
新建之后 | 可运行状态(从运行状态变为可运行状态) | 运行状态 | 阻塞状态 | 销毁状态 |
---|---|---|---|---|
|
|
|
|
|
|
|
|||
|
|
|||
4.wait线程收到其他线程发出的notify通知 |
|
|||
|
|
|||
|
start() 和run() | start(): 线程准备执行,具体执行由线程调度器决定 |
yield()和sleep() | yield(): 停止当前正在执行的线程,释放当前锁,让同样优先级的正在等待的线程有机会执行(只是有机会,具体怎么执行看系统,也可能还是自己执行) |
suspend()和resume() | suspend(): 使当前线程阻塞,不释放对象锁,只能被resume()恢复。 |
wait()和notify()和notifyAll() | wait(): 释放当前锁,等待被notify()通知 |
stop() | 停止线程,强制停止,不安全。 |
interrupt()和interrupted()和isInterrupted() | 中断线程。 调用该方法的线程的状态为将被置为"中断"状态。 并非真的立即停止。 更深的理解参考:阻塞(sleep等等)区别 中断(interrupt)+ 中断的意义 - baoendemao - 博客园 https://www.cnblogs.com/baoendemao/p/3804730.html |
每个锁对象都有两个对列,就绪队列和阻塞队列。
就绪队列:存储将要获得锁的线程。(线程被唤醒后才会进入就需队列,等待CPU的调度)
阻塞队列:存储了被阻塞的线程。(线程被wait之后,就会进入阻塞队列,等待下一次被唤醒)
END
《JAVA多线程编程核心技术》 笔记:第三章:线程间通信的更多相关文章
- Java多线程编程核心技术,第三章
1,notify的同步块完了,才会运行wait的同步块 2,interrupt()不是静态方法,用在wait的线程上会有InteruptException,锁也会被释放 3,notify()唤醒的线程 ...
- java多线程编程核心技术-笔记
一.第一章 1.自定义线程类中实例变量针对其他线程有共享和不共享之分,自定义线程中的变量,如果是继承自thread类,则每个线程中的示例变量的更改,不影响其他线程2.当多个线程去访问一个局部变量是会产 ...
- Java多线程编程核心技术,第六章
1,饿汉模式/单例模式,一开始就新建一个静态变量,后面用getInstance()都是同一个变量 2,懒汉模式/单例模式,在getInstance()才会new一个对象,在第一个有了后不会继续创建 3 ...
- Java多线程编程核心技术,第五章
1,Timer timer = new Timer(true)现在是守护进程 2,timer是按照顺的,没有异步 3,timer方法,schedule(TimerTask task, Date fir ...
- Java多线程编程核心技术,第四章
1,ReentrantLock 2,object的wait(),wait(x),notify(),notifyAll(),分别等于Condition的await(),await(x,y),signal ...
- 转:【Java并发编程】之十二:线程间通信中notifyAll造成的早期通知问题(含代码)
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17229601 如果线程在等待时接到通知,但线程等待的条件还不满足,此时,线程接到的就是早期 ...
- 【Java并发编程】之十二:线程间通信中notifyAll造成的早期通知问题
如果线程在等待时接到通知,但线程等待的条件还不满足,此时,线程接到的就是早期通知,如果条件满足的时间很短,但很快又改变了,而变得不再满足,这时也将发生早期通知.这种现象听起来很奇怪,下面通过一个示例程 ...
- Java多线程编程核心技术-第3章-线程间通信-读书笔记
第 3 章 线程间通信 线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大 ...
- Java多线程编程核心技术(三)多线程通信
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时 ...
- 《Java 多线程编程核心技术》- 笔记
作为业务开发人员,能够在工作中用到的技术其实不多.虽然平时老是说什么,多线程,并发,注入,攻击!但是在实际工作中,这些东西不见得用得上.因为,我们用的框架已经把这些事做掉了. 比如web开发,外面有大 ...
随机推荐
- docker运行环境安装-centos(一)
在这里我们使用的是docker的社区版Docker CE,针对的是未安装docker的新的主机,如果安装过docker的早期版本,先卸载它们及关联的依赖资源,安装的版本为docker 18.03. 1 ...
- python3 logging 日志记录模块
#coding:utf-8 import logginglogging.basicConfig(filename='log1.log', format='%(asctime)s -%(name)s-% ...
- js操作注意事项
1.函数赋值给变量时,不能加括号 function fun() { ... } var str=fun; 2.js创建构造函数和调用对象,对象内不能用var 变量,只能用this function f ...
- ES6中的export,import ,export default
ES6模块主要有两个功能:export和importexport用于对外输出本模块(一个文件可以理解为一个模块)变量的接口import用于在一个模块中加载另一个含有export接口的模块.也就是说使用 ...
- 【转载】 使用rman进行坏块修复(ORA-01578、ORA-01110)
[转自]http://blog.itpub.net/21256317/viewspace-1062055/ 使用rman进行坏块修复(ORA-01578.ORA-01110) 2012年的一天,处理的 ...
- [svc]jdk1.7.0_13(系列)下载url
蛋疼了,这个版本,找了老半天没找到 最后是同事找到的 http://download.oracle.com/otn/java/jdk/7u13-b20/jdk-7u13-linux-x64.tar.g ...
- Flask系列:数据库
这个系列是学习<Flask Web开发:基于Python的Web应用开发实战>的部分笔记 对于用户提交的信息,包括 账号.文章 等,需要能够将这些数据保存下来 持久存储的三种方法: 文件: ...
- ISO/IEC14443 ATS(Answer to Select)详解
原文: https://www.duoluodeyu.com/2322.html 发表评论 777 A+ 所属分类:智能卡 长度字节TL 格式字节T0 接口字节TA(1) 接口字节TB(1) 接口 ...
- spring-jmx.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...
- scala读写文件
def main(args: Array[String]): Unit = { //1 read for( i<- Source.fromFile("test.dat").g ...
1,notify的同步块完了,才会运行wait的同步块 2,interrupt()不是静态方法,用在wait的线程上会有InteruptException,锁也会被释放 3,notify()唤醒的线程 ...
一.第一章 1.自定义线程类中实例变量针对其他线程有共享和不共享之分,自定义线程中的变量,如果是继承自thread类,则每个线程中的示例变量的更改,不影响其他线程2.当多个线程去访问一个局部变量是会产 ...
1,饿汉模式/单例模式,一开始就新建一个静态变量,后面用getInstance()都是同一个变量 2,懒汉模式/单例模式,在getInstance()才会new一个对象,在第一个有了后不会继续创建 3 ...
1,Timer timer = new Timer(true)现在是守护进程 2,timer是按照顺的,没有异步 3,timer方法,schedule(TimerTask task, Date fir ...
1,ReentrantLock 2,object的wait(),wait(x),notify(),notifyAll(),分别等于Condition的await(),await(x,y),signal ...
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17229601 如果线程在等待时接到通知,但线程等待的条件还不满足,此时,线程接到的就是早期 ...
如果线程在等待时接到通知,但线程等待的条件还不满足,此时,线程接到的就是早期通知,如果条件满足的时间很短,但很快又改变了,而变得不再满足,这时也将发生早期通知.这种现象听起来很奇怪,下面通过一个示例程 ...
第 3 章 线程间通信 线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大 ...
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时 ...
作为业务开发人员,能够在工作中用到的技术其实不多.虽然平时老是说什么,多线程,并发,注入,攻击!但是在实际工作中,这些东西不见得用得上.因为,我们用的框架已经把这些事做掉了. 比如web开发,外面有大 ...
在这里我们使用的是docker的社区版Docker CE,针对的是未安装docker的新的主机,如果安装过docker的早期版本,先卸载它们及关联的依赖资源,安装的版本为docker 18.03. 1 ...
#coding:utf-8 import logginglogging.basicConfig(filename='log1.log', format='%(asctime)s -%(name)s-% ...
1.函数赋值给变量时,不能加括号 function fun() { ... } var str=fun; 2.js创建构造函数和调用对象,对象内不能用var 变量,只能用this function f ...
ES6模块主要有两个功能:export和importexport用于对外输出本模块(一个文件可以理解为一个模块)变量的接口import用于在一个模块中加载另一个含有export接口的模块.也就是说使用 ...
[转自]http://blog.itpub.net/21256317/viewspace-1062055/ 使用rman进行坏块修复(ORA-01578.ORA-01110) 2012年的一天,处理的 ...
蛋疼了,这个版本,找了老半天没找到 最后是同事找到的 http://download.oracle.com/otn/java/jdk/7u13-b20/jdk-7u13-linux-x64.tar.g ...
这个系列是学习<Flask Web开发:基于Python的Web应用开发实战>的部分笔记 对于用户提交的信息,包括 账号.文章 等,需要能够将这些数据保存下来 持久存储的三种方法: 文件: ...
原文: https://www.duoluodeyu.com/2322.html 发表评论 777 A+ 所属分类:智能卡 长度字节TL 格式字节T0 接口字节TA(1) 接口字节TB(1) 接口 ...
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...
def main(args: Array[String]): Unit = { //1 read for( i<- Source.fromFile("test.dat").g ...