线程间通信
1.使用wait/notify实现线程间通信
2.生产者/消费者模式实现
3.方法join的使用
4.ThreadLocal类的使用
等待/通知机制
1.不使用等待/通知机制实现线程间通信
通过while(true)轮询机制检查某一个条件
缺点:这样一个死循环们非常耗费cpu资源
2.什么是等待/通知机制
产生数据时间不确定,使用数据时间也不确定。
1.数据还未产生,数据使用者需要等待数据产生
2.数据产生后通知数据使用者,这时数据使用者才知道数据有了,可以使用数据
3.等待/通知机制的实现
方法wait()作用,使当前执行代码的线程进行等待,将当前线程置入到"预执行队列"中,等待接到通知或被中断为止
使用wait方法必须获得对象级别的锁,所以只能在同步方法或同步代码块中使用
wait方法执行后,当前线程放弃锁,在从wait返回之前,需要和其他线程竞争获取锁
方法notify()也需要获得对象级别的锁,所以也需要在同步方法或同步代码块中使用,如果有多个线程等待,线程规划器会随机
挑选出一个呈wait等待的线程
说明:执行notify()之后,当前线程并不会立马释放掉锁,而必须等待执行完synchronized代码块之后,同时wait()线程也不会立马获得锁
notifyAll()方法可以使所有正在等待队列中等待同一共享资源的全部线程从等待状态退出,进入可运行状态,到底哪个线程要想执行,取决于
线程执行的优先级,或者jvm的实现情况(随机的)。
每个锁对象有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,一个线程
被唤醒后之后就会进入到就绪队列,等待cpu调度,反之,当过一个线程wait之后就会进入阻塞队列。
4.方法wait()锁释放与notify()锁不释放
当方法wait()被执行之后,锁自动释放,但执行完notify()方法,锁不会立即释放
5.当interrupt方法遇到wait方法
当线程呈现wait状态时,调用线程对象的interrupt方法会出现异常InterruptedException
以上总结:执行完同步代码块会释放锁
执行同步代码块异常时,线程终止,会释放锁
执行wait方法,线程会立马释放掉锁,此线程对象进入线程等待池,等待被唤醒
6.只通知一个线程
调用notify()方法一次,只会随机通知一个线程进行唤醒
7.唤醒所有线程
notifyAll()
8.方法wait(long)的使用
带参数的wait(long)方法:等待某一时间是否有现成对锁进行唤醒,如果超过这个时间,就会自动唤醒1
9.通知过早
如果通知过早,则会打乱程序运行的正常逻辑,就如同先通知了,再等待了,所以就无限等待了
10.等待wait条件发生变化
如果wait等待条件发生变化,则会打乱程序运行的正常逻辑
11.生产者/消费者模式实现
等待/通知模式最经典的案例就是生产者/消费者模式
一生产与一消费:操作值
一对一执行
多生产与多消费:操作值 - 假死
主要原因:虽然代码中通过wait和notify通信,但是并不能保证生产者发出的通知就一定会被消费者执行,也有可能是生产者自己执行,长此下去,所有线程都会进入等待状态
假死的原因就是可能连续唤醒同类
解决方式,通知改为notifyAll()
一生产与一消费:操作栈
一生产与多消费:操作栈:解决wait条件改变与加色、
多生产与一消费:操作栈
多生产与多消费:操作栈
12.通过管道进行线程之间的通信(字节流)
一个线程发送数据到输出管道,另一个线程从输入管道中读数据
PipedInputStream和PipedOutputStream
PipedReader和PipedWriter
使用inputStream.connect(outputStream)或outputStream.connect(inputStream)的作用使两个Stream之间产生通信
13.实战:等待/通知之交叉备份
使用volatile关键字共享变量,再结合等待通知实现
方法join的使用
join等待线程对象销毁。主线程创建并启动子线程,子线程当中有很多的耗时任务,主线程往往要早于子线程提前结束,这时,如果主线程要等子线程结束后再结束,就要用到join
1.方法join前的铺垫
等子线程执行完再执行主线程
2.join方法解决
方法join作用是使所属线程对象x正常执行run方法,而使当前线程z进行无限期的阻塞,当x线程执行完之后再继续执行z线程后面的代码
底层用的是wait方法,操作的锁
join具有使线程排队的作用,有点类似于同步效果
join和synchronized区别:join内部使用wait方法进行等待,而synchronized使用的是对象监视器。
3.方法join与异常
join方法遇到interrupt方法遇到会抛出异常
例如线程x运行,y线程调用x线程,并join x线程,当y线程执行interrupt方法,y线程会抛出异常,但是x线程还是会继续运行。
4.方法join(long)的使用
方法join(long)中的参数是设定等待的时间
5.join(long)与sleep(loong)区别
join(long)内部使用的是wait(long)方法来实现的,所以具有释放锁的特点
Thread.sleep(long)不释放锁,会持有锁
6.方法join()后面的代码提前运行:出现意外
join必须得到锁,才会继续运行
类ThreadLocal的使用
变量值的共享可以使用public static变量的形式,所有线程都是用同一个public static变量,这个只能存一个值,并不是对象,list,map、等
实现每个线程有自己的共享变量 要用到ThreadLocal
1.方法get与null
类ThreadLocal解決的是变量在不同线程间的隔离性,也就是不同线程拥有自己的值,不同线程的值可以放在ThreadLocal类中进行保护
2.验证线程变量的隔离性
第一次get()返回的是null值
3.解决get()返回null值问题
创建一个类,继承自ThreadLocal,覆盖他的初始化方法initialValue(),给他初始化的值
4.再次验证线程变量的隔离性
类InheritableThreadLocal的使用
在子线程中取得父线程继承下来的值,如果子线程在取值的同时,主线程改变了值,这时候,子线程得到的还是旧值