一:等待/通知机制
线程与线程之间不是独立的个体,它们之间可以互相通信和协作。
1: 不需要等待/通知机制实现线程间的通信:
2:等待/通知机制
多个线程之间可以实现通信,原因是多个线程共同访问同一个变量,但这种机制不是“等待/通知”,两个线程完全是主动地去读取一个共享变量,在花费读取时间的基础上,读到的值不是想要的,不能完全确定。所以需要一种“等待/通知”机制来满足需求。
3:等待/通知机制的实现
方法wait()的作用是使当前执行代码的线程进行等待,wait()是Object类的方法,该方法用来将当前线程置于“预执行队列”中,并且wait()所在的代码行处停止执行 ,直到通知被中断为止。
在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步块中调用wait()方法。
在执行wait()方法后,当前线程释放锁,抛出IllegalMonitorStateException,不需try-catch语句进行捕获异常。
方法notify()也需在同步方法或者同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果在调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。
该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑出其中一个呈wait状态的线程,对其发出通知notify,并使其等待获取该对象的对象锁。
执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也也并不能马上获取该对象锁,要等到notify()方法的线程将程序执行完,也就是出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。当第一次获得该对象锁的wait线程运行完毕以后,它会释放该对象锁,如果此时该对象没有再次使用notify语句,则即该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。(即notify方法一次只能唤醒一个锁)
wait使线程停止运行,而notify使停止的线程继续执行。
关键字synchronized可以将任何一个Object对象作为同步对象来看待,Java为每个对象都实现了wait()和notify()方法,它们必须用在被synchronized同步的Object的临界区内。
wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。
notify()方法可以随机唤醒等待队列中等待同一共享资源的“一个”线程”,并使该线程退出等待队列,进入可运行状态,这就是notify()方法仅通知“一个”线程。
notifyAll()方法可以使所有正在等待队列中等待同一资源的“全部”线程从等待状态退出,进入可运行状态。优先级高的那个线程最先执行,但也有可能随机执行,则取决于JVM虚拟机。
4:方法wait()锁释放与notify()锁不释放
当方法wait()被执行后,锁被自动释放,但执行完notify()方法,锁却不会马上释放。
sleep()方法不释放锁。
当前对象线程(Lock)在同步代码块中执行notify(),线程对象(Lock)不会马上释放锁,而是必须执行完notify()方法所在的同步synchronized代码块后才释放锁。
5:当interrupt方法遇到wait()方法
但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。
如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到wait()/sleep()/join()后,就会立刻抛出InterruptedException。
Thread.interrupt() VS Thread.stop()
这两个方法最大的区别在于:interrupt()方法是设置线程的中断状态,让用户自己选择时间地点去结束线程;而stop()方法会在代码的运行处直接抛出一个ThreadDeath错误,这是一个java.lang.Error的子类。所以直接使用stop()方法就有可能造成对象的不一致性。
6:只通知一个线程
方法notify()仅随机唤醒一个线程,
在多次调用notify()方法时,会随机将等待wait状态的线程进行唤醒。多次调用notify()方法会唤醒全部WAITING中的线程
7:唤醒全部线程
notifyAll()方法会唤醒全部线程。
8:方法wait(long)的使用
带参数的wait(long)方法的功能是将等待某一时间内是否有线程对锁进行唤醒,如果超时则自动唤醒。
9:通知过早
如果先通知notify()方法 thread.sleep(),则wait()方法后的代码即不会被执行。(等待被唤醒---》方法wait永远不会被通知)或者notify()方法改变了对象的值,使其wait()方法的同步代码块不符合条件,则不执行。
如果先wait()方法 thread.sleep(),则notify()方法执行完synchronized代码块后的代码,再去执行wait()后的代码。
10:等待wait的条件发生变化
在使用wait()/notify()模式时,如果wait()的条件发生变化,则会造成程序逻辑的混乱。用while()只要条件不成立就不会执行,少用 i f { }判断。
if 与 while判断条件的合理使用
11:生产者/消费者模式实现
等待/通知模式最经典案例就是“生产者/消费者”模式。其原理都是基于wait/notify。
1)一生产与一消费:操作值
创建两个线程对象,一个生产者线程,一个消费者线程。
2)多生产与多消费:操作值---假死
线程全部进入WAITING状态,则程序就不会再执行业务功能了,整个项目呈停止状态。即“假死”。
呈假死状态的进程中所有的线程都呈WAITING状态。
代码中通过wait/notify进行通信,但不能保证notify唤醒的是异类或者同类。则线程不能继续运行下去,大家都在等待,则呈WAITING状态。
3)多生产与多消费者:操作值
notify()改为notifyAll()通知所有类,防止“假死”状态。
4)一生产与一消费:操作栈
生产者使用堆栈List对象中放入数据,消费者从List堆栈中取出数据,List容量为1。
5)一生产与多消费---操作栈:解决wait条件改变与假死
使用一个生产者向堆栈List对象中放入数据,而多个消费者从List堆栈中取出数据,List容量为1。
6)多生产与一消费:操作栈
使用生产者向堆栈List对象中放入数据,而多个消费者从List堆栈中取出数据,List容量为1。
7)多生产与多消费:操作栈
使用生产者向堆栈List对象中放入数据,而多个消费者从List堆栈中取出数据,List容量为1。
12:通过通道进行线程间的通信:字节流
管道流:用在不同线程间直接传输数据,实现不同线程间的通信。
1)PipedInputStream和PipedOutputStream
PipedInputStream inputStream=new PipedInputStream()
PipedOutputStream outputStream=new PipedOutputStream()
读取线程new ThreadRead(outputStream)启动。使用inputStream。connect(outputStream)或outputStream(inputStream)实现两个Stream之间的通信连接。
13:通过通道进行线程间的通信:字符流
1)PipedReader和PipedWriter
14:等待/通知之交叉备份
原理:volatile private Boolean prevIsA=false;
二:方法join的使用
方法join()的作用是等待线程对象的销毁。方法join具有使线程排队运行的作用,类似于同步的运行效果。
join与synchronized的区别:
join在内部使用wait()方法进行等待,而synchronized关键字使用的是“对象监视器”原理作为同步。
在join过程中,如果当前线程对象被中断,则当前线程出现异常,
方法join(long)设定等待的时间
join()与sleep()方法对同步处理上的区别:
方法join(long)的功能在内部使用wait(long)方法来实现的,所以方法join()方法具有释放锁的特点。而sleep(long)方法不释放锁。
方法join()后面的代码提前运行:出现意外。
三:类ThreadLocal的使用
public static 变量的形式可以实现变量值的共享, 所有的线程可以使用同一个public static 变量。类ThreadLocal实现每一个线程都有自己的共享变量。
类ThreadLocal主要解决的是使每个线程绑定自己的值,可以将ThreadLocal类比喻为全局存放数据的盒子,盒子中可以存储每个线程的私有数据。
类ThradLocal解决的是变量在不同线程间的隔离性,也就是不同线程拥有自己的值。及使每个线程都有自己的私有属性。
get()返回null 问题———》让实现类继承ThreadLocal类,并覆盖initialValue()方法,使其方法具有初始值,返回初始值。
子线程和父线程各有各自所拥有的的值,不存在继承关系。
四:类InheritableThreadLocal的使用
使用类 InheritableThreadLocal可以在子线程中取得父线程继承下来的值。 注意:如果使用InheritableThreadLocal类,字线程在取得值的同时,父线程将InheritableThreadLocal中的值进行更改,那么子线程的值还是旧值不会改变。
以下文章来自:http://blog.csdn.net/linxdcn/article/details/72819817?fps=1&locationNum=13
线程状态
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
- NEW 状态是指线程刚创建, 尚未启动
- RUNNABLE 状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等
- BLOCKED 这个状态下, 是在多个线程有同步操作的场景, 这个事件将在另一个线程放弃了这个锁的时候发生,也就是这里是线程在等待进入临界区
- WAITING(无线等待) 这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在临界点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束
- TIMED_WAITING 这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态
- TERMINATED 这个状态下表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)
java层次的状态转换图
操作系统层次的状态转换图
(2)wait()和notify/notifyAll()方法
wait()方法
- 线程进入WAITING状态,并且释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁,等待其他线程调用“锁标志“对象的notify或notifyAll方法恢复
- wait方法是一个本地方法,其底层是通过一个叫做监视器锁的对象来完成的,所以调用wait方式时必须获取到monitor对象的所有权即通过Synchronized关键字,否则抛出IllegalMonitorStateException异常
notify/notifyAll()方法
- 在同一对象上去调用notify/notifyAll方法,就可以唤醒对应对象monitor上等待的线程了。notify和notifyAll的区别在于前者只能唤醒monitor上的一个线程,对其他线程没有影响,而notifyAll则唤醒所有的线程
(3)sleep/yield/join方法解析
sleep
- sleep方法的作用是让当前线程暂停指定的时间(毫秒)
- wait方法依赖于同步,而sleep方法可以直接调用
- sleep方法只是暂时让出CPU的执行权,并不释放锁。而wait方法则需要释放锁
yield
- yield方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止
- yield只能使同优先级或更高优先级的线程有执行的机会
join
- 等待调用join方法的线程结束,再继续执行。如:t.join(),主要用于等待t线程运行结束
- 作用是父线程等待子线程执行完成后再执行,换句话说就是将异步执行的线程合并为同步的线程
(4)不推荐使用方法解释
参考:Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated?
suspend()和resume()
- 这两个方法是配套使用的,suspend()是暂停线程,但并不释放资源,容易造成死锁情况
stop()
- 因为调用stop会使线程释放所有的锁,导致不安全情况,在调用stop时候,由锁保护的临界区可能处于状态不一致的情况,这不一致状态将暴露给其他线程
- 推荐的做法是,维护一个状态变量,当线程需要停止时更改这一状态变量,该线程应检查这一状态变量,看该线程是否应该终止了
(5)关于interrupt()中断函数
- 其实调用这个函数并不是真的中断线程,这个函数只是将Thread中的interrupt标志设置为true,用户需自行检测这一变量,停止线程,这种做法避免了stop带来的问题
推荐:http://blog.csdn.net/zolalad/article/details/38903911