一、等待/通知机制的实现
使用wait()和notify()实现线程间的通信,wait使线程停止运行,而notify使停止的线程继续运行
方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或者被中断为止。在调用wait()之前,线程必须获取该对象的对象级别锁,即只能在同步方法或者同步快中调用wait()方法。在执行wait()方法后,当前线程释放锁。如果在调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateExcepion,它是RunntimeException的一个子类,因此,不需要try-catch语句进行捕获异常
方法notify()也要在同步方法或者同步块中调用,即在调用前,线程也必须获取该对象的对象级别锁。如果在调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateExcepion。该方法用来通知那些可能等待该对象的对象锁的线程,如果有多个线程等待,则由线程规划器随机挑出其中一个呈wait()状态的线程,对其发出通知notify(一次notify方法只能通知一个执行wait方法的线程),并使它等待获取该对象的对象锁。需要注意的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait()状态的线程并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait()状态所在线程才可以获取该对象锁。当第一个获取该对象锁wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次调用notify语句,则即便该对象已空闲,其他wait状态等待线程由于没有收到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll,如果发出notify操作时没有处于阻塞状态中的线程,那么该命令会被忽略。
方法notifyAll()方法可以使所有正在等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的那个想成优先执行,但有可能是随机执行,因为这取决于虚拟机的实现
二、等待/通知简单例子
例子一:
public class MyThread extends Thread {
private Object lock;
public MyThread(Object lock) {
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
System.out.println("开始wait。。" + System.currentTimeMillis());
lock.wait();
System.out.println("结束wait。。" + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyThread2 extends Thread {
private Object lock;
public MyThread2(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println("开始 notify。。" + System.currentTimeMillis());
lock.notify();
System.out.println("结束 notify。。" + System.currentTimeMillis());
}
}
public static void main(String[] args) {
try {
Object lock = new Object();
MyThread t = new MyThread(lock);
t.start();
Thread.sleep(3000);
MyThread2 t2 = new MyThread2(lock);
t2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行main方法结果:
开始wait。。1546948292756
开始 notify。。1546948295759
结束 notify。。1546948295759
结束wait。。1546948295759
结论:第一个线程在3s后被第二个线程唤醒
例子二:
public class ThreadB extends Thread {
private MyList list;
public ThreadB(MyList list) {
this.list = list;
}
@Override
public void run() {
try {
synchronized (list) {
if (list.size() != 5) {
System.out.println("wait begin " + System.currentTimeMillis());
list.wait();
System.out.println("wait end " + System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private MyList list;
public ThreadA(MyList list) {
this.list = list;
}
@Override
public void run() {
try {
synchronized (list){
for (int i = 0; i < 10; i++) {
list.add();
if (list.size() == 5){
list.notify();
System.out.println("已发出通知");
}
System.out.println("添加了" + (i + 1) + "个元素");
Thread.sleep(100);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
MyList service = new MyList();
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
}
}
运行main方法后结果:
wait begin 1546952240174
添加了1个元素
添加了2个元素
添加了3个元素
添加了4个元素
已发出通知
添加了5个元素
添加了6个元素
添加了7个元素
添加了8个元素
添加了9个元素
添加了10个元素
wait end 1546952241201
从结果看,wait end在最后输出,这也说明了notify()方法执行后并不立即释放锁
三、线程状态转变的示意图:
参考https://www.aliyun.com/jiaocheng/820348.html
补充:每个锁对象都有两个队列,就绪队列和阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度,反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。
四、当interrupt方法遇见wait方法
当线程呈wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常
public class Service {
public void test(Object lock) {
try {
synchronized (lock) {
System.out.println("wait begin " + System.currentTimeMillis());
lock.wait();
System.out.println("wait end " + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
Object lock = new Object();
Thread1 a = new Thread1(lock);
a.start();
Thread.sleep(5000);
a.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Thread1 extends Thread {
private Object lock;
public Thread1(Object lock) {
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.test(lock);
}
}
运行main结果
wait begin 1546957612257
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at threadtest.xianchengjiantongxin.Service.test(Service.java:11)
at threadtest.xianchengjiantongxin.Thread1.run(Thread1.java:17)
总结:1、执行完同步代码块就会释放对象的锁
2、执行同步代码块的过程中,遇到异常导致线程终止,锁与会被释放
3、在执行同步代码块过程中,执行了所属对象的wait方法,这个线程会释放对象锁,而此线程对象会进入线程等待池中,等待被唤醒。
五、wait(long)
带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。也可以由其他线程唤醒,如果该锁设置了自动唤醒时间,但是在未到时间这段时间有其他线程用notify或notifyAll唤醒,则不等到达自动唤醒时间,直接唤醒。