前言:
线程最大的问题就是抢占式执行,随即调度,而我们写的代码,不希望它是随机的,随机就代表着不可控,出现bug的概率会大大提高.
所以,程序员发明了一些办法,来控制程序之间的执行顺序.虽然线程在内核里的调度是随机的,但是可以通过一些api让线程主动阻塞,主动放弃cpu(给别的线程让路).
比如,t1和t2两个线程,希望t1先执行,执行的差不多了,在让t2来干.就可以让t2先wait(阻塞,主动放弃cpu),等t1执行的差不多了,再通过notify来通知t2,把t2唤醒,让t2接着干.
那么上述场景,使用join或者sleep行不行呢?
使用join,则必须让t1彻底执行完,t2才能运行.如果是希望t1先干50%的活,就让t2开始行动,此时join无能为力.
使用sleep,指定一个休眠的时间.但是t1执行完这些代码,到底花了多少时间,不好估计.
使用wait和notify可以更好的解决上述的问题.
wait:
wait进行阻塞,某个线程调用wait方法,就会进入阻塞(无论是通过哪个对象wait的),此时就处在WAITING状态.
wait,notify和notifyAll这几个类都是Object类的方法,所以Java里随便一个对象,都可以有这三种方法.
注意,wait也需要这个异常,这个异常,很多带有阻塞功能的方法都带.
这些方法都是可以被interrupt方法通过这个异常给唤醒的.
wait不加任何参数,就是一个"死等".一直等待,直到有其他线程唤醒它.
我们运行这段代码,会发现日志里报了这样的异常.
IllegalMonitorStateException,非法的锁状态异常.
为什么会有这个异常??
要理解wait要进行哪些操作:
那么,上述的锁状态异常就是因为,还没加锁呢,就要释放锁.
因此,wait操作需要搭配synchronized来使用.
搭配synchronized之后,我们可以看到,main线程阻塞等待了.
虽然这里wait是阻塞了,阻塞在synchronized的代码块里,但实际上,这里的阻塞时释放了锁的.
此时其他的线程是可以获取到object这个对象的锁的.此时这里的阻塞,就处在waiting状态.
wait和notify的实现过程
务必要这四个对象都相同,才能够正确生效.
此处的通知得和wait配对,如果wait使用的对象和notify使用的对象,不同,此时的notify不会有任何的效果.(notify只能唤醒在同一个对象上等待的线程).
如果代码这里写作t1.start 和 t2.start,由于线程调度的随机性,此时不能保证一定是先执行wait,后执行notify,
如果调用notify,此时没有线程wait,此处的wait是无法被唤醒的.(相当于notify空打了一炮).
这种通知就是无效通知,也没有什么副作用.
因此此处的代码,还是要尽量保证先执行wait,后执行notify.才是有意义的.
运行这段代码:
此处,先执行了wait,很明显wait操作阻塞了,没有看到wait之后的打印;接下来,执行到了t2,t2进行了notify的时候,才会把t1的wait唤醒,t1才能继续执行.
只要t2不执行notify,此时的t1就会始终wait下去.(死等不是一个很好的选择)
wait无参数版本,就是死等的.
wait带参数的版本,指定了等待的最大时间.
wait带参数的版本和sleep的区别:
虽然都是指定等待的时间,虽然也都能被提前唤醒(wait使用的是notify唤醒,sleep使用的是interrupt唤醒),但是这里表示的含义截然不同.
notify唤醒wait,这里不会有任何的异常(正常的业务逻辑).
interrupt唤醒sleep,则是出异常(表示一个出问题的逻辑).
如果当前有多个线程在等待object对象,此时有一个线程object.notify(),此时是随机唤醒一个等待的线程(不知道具体是哪一个).
但是,这种场景下,我们可以用多组不同的对象,来指定唤醒的顺序.
public class ThreadDemo18 {
public static void main(String[] args) {
//有三个线程,分别只能打印A,B,C,控制三个线程固定按照ABC的顺序打印
Object locker1 = new Object();
Object locker2 = new Object();
Thread t1 = new Thread(()->{
System.out.println("A");
synchronized (locker1) {
locker1.notify();
}
});
Thread t2 = new Thread(()->{
synchronized (locker1) {
try {
locker1.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("B");
synchronized (locker2) {
locker2.notify();
}
});
Thread t3 = new Thread(()->{
synchronized (locker2) {
try {
locker2.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("C");
});
t2.start();
t3.start();
//此处加sleep,是为了保证t2,t3线程先执行,先让这两个线程获取到锁,防止t1产生无效通知.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t1.start();
}
}
notifyAll和notify很相似,多个线程wait的时候,notify随机唤醒一个,notify则是所有的线程都唤醒,这些线程再一起竞争锁.