1. Java线程通信最常见的两种方式:
1)synchronized加锁的线程调用Object类的wait()/notify()/notifyAll()
2)ReentrantLock类加锁的线程调用Condition类的await()/signal()/signalAll()
2.先来看第一种方式。
wait()/notify()/notifyAll()
1) wait()/notify()/notifyAll()是native,是final不能被重写
2)wait方法可以使得当前线程阻塞,并且当前线程必须拥有对象的monitor
3)notify能够唤醒一个正在等待该对象的mointor lock的线程,这个方法只能唤醒其中一个线程
4)notifyAll能够唤醒所有正在等待该对象的mointor lock的线程
需要注意的是:
1)使用wait没有获取到该对象的monitor lock将会抛出IllegalMonitorStateException,我们看一个例子:
public class GY{
public static void main(String[] args) {
String lock = new String("test");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2)调用某个对象的wait方法会使得当前线程阻塞,wait方法之后的代码将不会去执行
再看一个例子:
public class GY{
public static void main(String[] args) {
String lock = new String("test");
synchronized (lock) {
try {
lock.wait();
System.out.println("the current thread is "+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3)wait方法调用之后会释放掉当前对象的锁
4) notify方法唤醒等待当前对象锁的一个线程,但是该方法执行之后并不会立即释放锁,出了同步代码块才会将锁释放
验证3,4,再看一个例子
public class GY{
public static void main(String[] args) {
String lock = new String("test");
new Thread("A"){
@Override
public void run() {
synchronized (lock){
try {
lock.wait();
System.out.println("the current thread is "+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread("B"){
@Override
public void run() {
synchronized (lock){
lock.notify();
System.out.println("the current thread is "+Thread.currentThread().getName());
}
}
}.start();
}
}
5)wait方法是可中断方法,可中断方法会收到中断异常InterruptedException,同时interrupt标识会被擦除。
对5的验证:
public class GY{
public static void main(String[] args) {
String lock = new String("test");
Thread threadA = new Thread("A"){
@Override
public void run() {
synchronized (lock){
try {
lock.wait();
System.out.println("the current thread is "+Thread.currentThread().getName());
} catch (InterruptedException e) {
System.out.println("the thread has been interrupted and the flag is "+Thread.currentThread().isInterrupted());
}
}
}
};
threadA.start();
threadA.interrupt();
}
}
同时一个疑问:wait()/notify()/notifyAll()为什么定义在Object中?
原因就是每个对象都有monitor,让当前线程等待某个对象的monitor lock,当然就需要通过该对象来操作,而不是通过当前线程去操作。
2.1.wait()和sleep()方法之间的区别和联系
从表面上看,wait()和sleep()都可以使得当前线程进入阻塞状态,但是两者之间存在本质性的差别,下面总结两者的区别和相似之处:
1)wait()和sleep()都可以使得线程进入阻塞状态
2) wait()和sleep()都是可中断方法,被中断之后都会收到中断异常
3) wait()是Object类中的方法,由于wait()调用必须在一个synchronized方法/块中,调用之前需要先获取对象的monitor,每个对象都有自己的monitor,让当前线程等待当前对象的monitor,当然需要当前对象来操作,所以wait()方法就必须要定义在Object类中, 而sleep()是Thread特有的方法
4) wait()方法执行必须在同步方法/同步块中,而sleep()不需要
5) 线程在同步方法中执行sleep()时,并不会释放掉monitor的锁,而wait()会释放掉
6) Sleep()短暂休眠之后会主动退出阻塞,而wait()(没有指定时间)则需要被其他线程中断后/其他线程唤醒并获取到当前对象的monitor后才能退出阻塞
2.2.生产者-消费者模型
生产者:生产商品 - 某一个地方
消费者:消费商品 - 某一个地方
在这里的某一个地方,在JAVA中就是队列:该队列需要可阻塞的put/take方法,如果队列已经满了那么put方法将阻塞直到有空间可用,如果队列已经空了,那么take方法将会阻塞直到有元素可用。
在代码的实现中:
队列 - 共享资源
生产者 - 生产数据,将数据放入到队列中
生产者 - 消费数据,从队列中消费数据
class SharingQueue<E>{
private final LinkedList<E> queue = new LinkedList<>();
private static final int defaultMaxValue = 5;
private final int max;
public SharingQueue(){
this(defaultMaxValue);
}
public SharingQueue(int size){
this.max = size;
}
public void put(E value){
synchronized (queue){
if(queue.size() >= max){
System.out.println(Thread.currentThread().getName() + ": queue is full!!!");
try {
queue.wait();//使得当前线程进入阻塞状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": the new data " + value+" has been produduted");
queue.addLast(value);
queue.notify();
}
}
public E take(){
synchronized (queue){
if(queue.isEmpty()){
System.out.println(Thread.currentThread().getName() + ": queue is empty!!!");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
E result = queue.removeFirst();
queue.notifyAll();//不会立即释放锁
System.out.println(Thread.currentThread().getName() + ": the data" + result+" has been handled");
return result;
}
}
}
public class GY{
public static void main(String[] args) {
SharingQueue<Integer> queue = new SharingQueue<>();
new Thread("Producer"){
@Override
public void run() {
while(true){
queue.put((int)(1+Math.random()*1000));
}
}
}.start();
new Thread("Consumer"){
@Override
public void run() {
while(true){
queue.take();
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
目前这个例子只是单线程,但是如果是多个线程的时候就会出现问题,也就是单线程通信的生产者消费者模型可能会出现的问题:
1)queue中没有元素仍然调用了removeFirst方法
threadA , threadB - take 陷入了阻塞,threadC - put 唤醒threadA, threadA执行
notify方法唤醒threadB
2)queue中元素超过阀值仍然调用了addLast方法
对这个问题的改进就是:将临界值更改为while判断 ,将notify改为notifyAll,改进后的代码如下:
class SharingQueue<E>{
private final LinkedList<E> queue = new LinkedList<>();
private static final int defaultMaxValue = 5;
private final int max;
public SharingQueue(){
this(defaultMaxValue);
}
public SharingQueue(int size){
this.max = size;
}
public void put(E value){
synchronized (queue){
while(queue.size() >= max){
System.out.println(Thread.currentThread().getName() + ": queue is full!!!");
try {
queue.wait();//使得当前线程进入阻塞状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": the new data " + value+" has been produduted");
queue.addLast(value);
queue.notifyAll();
}
}
public E take(){
synchronized (queue){
while(queue.isEmpty()){
System.out.println(Thread.currentThread().getName() + ": queue is empty!!!");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
E result = queue.removeFirst();
queue.notifyAll();//不会立即释放锁
System.out.println(Thread.currentThread().getName() + ": the data" + result+" has been handled");
return result;
}
}
}
public class GY{
public static void main(String[] args) {
SharingQueue<Integer> queue = new SharingQueue<>();
new Thread("Producer"){
@Override
public void run() {
while(true){
queue.put((int)(1+Math.random()*1000));
}
}
}.start();
new Thread("Consumer"){
@Override
public void run() {
while(true){
queue.take();
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
在这里我们为什么要改为notifyAll()呢,首先要知道两个概念:
a.锁池:threadA拥有某个对象的monitor,而其他线程想要调用这个对象synchronzied方法(synchronzied块),由于这些线程在进入对象的synchronzied方法(synchronzied块)之前必须先获得该对象的monitor lock,但是此时已经被threadA占有,所以这些线程就会进入到该对象的锁池中。
b.等待池:threadA调用了某个对象的wait方法,threadA就会释放该对象的锁后,进入该对象的等待池中。
2.3.下来我们再来介绍原因,也就是notify()和notifyAll()的区别与联系:
1)notify只是唤醒一个正在wait当前对象锁的线程,而notifyAll唤醒所有。值得注意的是:notify是本地方法,具体唤醒哪一个线程由虚拟机控制;如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知
2)当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只有一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
3) 调用notify和notifyAll方法后,当前线程并不会立即放弃锁的持有权,而必须要等待当前同步代码块执行完才会让出锁
4) 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
3.再看第二种方式:await()/signal()/signalAll()
我们可以知道Condition代替了Object当中的wait()/notify()/notifyAll()方法,以此实现线程间通信;Condition是一个接口, 基本方法是await()/signal()/signalAll();Condition依赖Lock接口,生成一个Condition对象需要lock.newCondition()得到Condition对象,调用await()/signal()/signalAll()。
需要注意:
1)使用await没有使用lock.lock()加锁将会抛出IllegalMonitorStateException
验证:
public class TestDemo {
public static void main(String[] args) {
//创建Lock对象
ReentrantLock lock = new ReentrantLock();
//得到condition对象
Condition con = lock.newCondition();
new Thread("A"){
@Override
public void run() {
try {
con.await();
System.out.println("the current thread is " +Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
2) await是可中断方法,可中断方法会收到中断异常InterruptedException,同时interrupt标识会被擦除
3)调用某个Condition对象的await方法会使得当前线程阻塞,调用await方法之后的代码将不会执行
4)await方法之后释放掉当前的lock
验证2)3)4)
public class TestDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition con = lock.newCondition();
new Thread("A"){
@Override
public void run() {
lock.lock();
try {
con.await();
System.out.println("the current thread is "+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread("B"){
@Override
public void run() {
try{
lock.lock();
con.signal();
System.out.println("the current thread is "+Thread.currentThread().getName());
} finally {
lock.unlock();
}
}
}.start();
}
}
5)signal方法唤醒等待当前锁的一个线程,但是该方法执行后不会立即释放锁,出了同步块才会将锁释放。
验证:
public class TestDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition con = lock.newCondition();
Thread threadA = new Thread("A"){
@Override
public void run() {
lock.lock();
try {
con.await();
System.out.println("the current thread is "+Thread.currentThread().getName());
} catch (InterruptedException e) {
System.out.println("the thread has been interrupted and the flag is "+Thread.currentThread().isInterrupted());
} finally {
lock.unlock();
}
}
};
threadA.start();
threadA.interrupt();
}
}
**4.提问:**await方法使用绑定在一个Condition对象上,一个ReentantLock对象可以拥有多个Condition对象,这些对象有什么作用?
答案就是wait只能在synchronized同步的对象中使用一个等待队列,而await对应的Lock可以有多个Condition对象,也就是说一个Lock可以有多个等待队列,相当于一个房子有多个门; synchronized块或者方法对于一个对象的一个queue,如果存在多个操作,比如isFull,isEmpty,add,remove,这样会涉及到上下文切换的性能损耗和获取锁的性能损耗。
4.使用await()/signal()/signalAll()实现生产者消费者模型
class EventQueue<E>{
private final LinkedList<E> queue = new LinkedList<>();
private static final int defaultMaxValue = 5;
private final int max;
//创建Lock对象
private final Lock lock = new ReentrantLock();
//创建一个关于put的Condition
private Condition putCon = lock.newCondition();
//创建一个关于take的Condition
private Condition takeCon = lock.newCondition();
public EventQueue(){
this(defaultMaxValue);
}
public EventQueue(int size){
this.max = size;
}
public void put(E value){
try{
lock.lock();
while(queue.size() >= max){
System.out.println(Thread.currentThread().getName() + ": queue is full!!!");
putCon.await();
}
System.out.println(Thread.currentThread().getName() + ": the new data " + value+" has been produduted");
queue.addLast(value);
takeCon.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void take(){
try{
lock.lock();
while(queue.isEmpty()){
System.out.println(Thread.currentThread().getName() + ": queue is empty!!!");
takeCon.await();
}
E result = queue.removeFirst();
putCon.signalAll();
System.out.println(Thread.currentThread().getName() + ": the data" + result+" has been handled");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class TestDemo {
public static void main(String[] args) {
EventQueue<Integer> queue = new EventQueue<>();
for(int i=0; i<3; i++){
new Thread("Producer-"+i){
@Override
public void run() {
while(true){
queue.put((int)(1+Math.random()*1000));
}
}
}.start();
new Thread("Consumer-"+i){
@Override
public void run() {
while(true){
queue.take();
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
}