可以在synchronized关键字下使用:
使用wait/notify线程通信:
wait/notify的原理如下
锁对象.wait():让该线程进入Monitor的WaitSet中,并释放当前锁。调用此方法后,当前线程将释放对象监控权 ,然后进入等待WAITING状态。在当前线程被notify后会立刻进入Monitor的EntryList中处于BLOCLED状态,等待锁的释放然后重新竞争锁,竞争成功后从断点处继续代码的执行。
用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码;而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
sleep和wait方法的异同:
相同:1、两种方法都能造成线程的阻塞。
不同:1、sleep方法原始定义在Thread类中,wait方法原始定义在Object类中;
2、sleep方法可以在任何位置使用,wait方法只能使用在同步代码块和同步方法中(只能配合synchronized);
3、sleep方法不会释放锁,wait方法会释放锁;
4、sleep方法只能阻塞有限时间,wait方法可以一直阻塞。
锁对象.notify():唤醒随机一个WaitSet的线程,该方法存在虚假唤醒,即唤醒线程后发现条件不满足。
锁对象.notifyAll():唤醒所有WaitSet中的线程,一般都用这个方法。
▲这三种方法只能在synchronized(同步代码块和同步方法)中使用;
▲notify()和notifyAll()调用后要等到当前线程走完synchronized释放锁后才能让被唤醒线程开始争夺
▲三种方法只能由当前同步代码块或同步方法中的“锁”来调用;
▲因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。
线程通信使用模板:
synchronized(lock) {
while(条件不成立) {
lock.wait();
}
// 干活
}
//另一个线程
synchronized(lock) {
lock.notifyAll();
}
保护性暂停
用在一个线程等待另一个线程的一个执行结果,相当于简化版的生产者消费者模型;
有一个结果需要从一个线程传递到另一个线程,让他们关联同一个GuardedObject,如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者);
JDK 中,join 的实现、Future 的实现,采用的就是此模式。因为要等待另一方的结果,因此归类到同步模式。
保护性暂停的模板:
把线程间要返回的用于线程通信的结果数据设置成成员变量,生成存取方法,两方法通过wait/notifyAll通信。
异步模式之生产者消费者模式:
与前面的保护性暂停中的 GuardObject 不同,不需要产生结果和消费结果的线程一一对应;
消费队列可以用来平衡生产和消费的线程资源。生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据;
消息队列是有容量限制的,满时不会再加入数据,空时不会再消耗数据;
JDK 中各种阻塞队列,采用的就是这种模式 。因为不需要等待消费者线程返回的值,只用专注生产,所以异步。
消息队列模板(理论上只用创造这一个类就行,分别在两个线程里使用存取方法即可)
把消息队列看成成员变量list,把要存取的元素看成list的元素,生成存取方法,互相wait/notifyAll通信。
在Lock接口实现类下的线程通信:
Condition.await()底层是调用LockSupport.park()来实现阻塞当前线程的。
await/signal:
可以用同一个锁创建多个休息室,这里的休息室不是synchronized的Waitset,而是conditionObject,对不同的线程调用await方法让他们进入不同的休息室 。其他用法和wait/notify相同
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
condition.await():在线程中调用这个方法表明让线程进入这个休息室,用法和wait()相同。
condition.signal():用法和notify()相同。
condition.signalAll():用法和notifyAll()相同。
使用LockSupport类中的park和unpark方法,但是LockSupport中的park/unpark方法底层使用的是Unsafe类的
park/unpark方法:
park/unpark的原理如下
LockSupport.park():
每个线程都有自己的一个 Parker 对象,内部由三属性组成 _counter、 _condition 条件变量 和 _mutex互斥锁
当线程调用 park() 方法时,先检查 _counter,如果为 0,则获得 _mutex锁 然后进入 _cond 阻塞,最后设置 _counter = 0;如果为1,则不进入阻塞接着运行,最后设置counter为0。
park方法不会释放锁。
LockSupport.unpark(线程对象):
先park再unpark:
先park会让线程阻塞,此时counter属性为0,unpark时先把counter置为1,再唤醒线程,释放互斥锁,最后counter=0
先unpark再park:
先把counter置为1,此时没有线程唤醒,park时先检查counter,此时为1,这时不进入阻塞继续运行,最后将counter置为0。
和wait/notify相比的特点:
1、可以先也可以后
2、不需要用锁或condition调用
3、阻塞和唤醒时都是针对某个线程