wait和notify都是Object类下的方法
作用:控制线程执行顺序
关于wait
wait的作用:
1.释放当前线程的锁对象
2.使此线程进入阻塞队列
3.满足一定条件时被唤醒,此时会重新尝试获取锁对象
wait必须搭配synchronized使用,不然会抛出异常
public static void main(String[] args) throws InterruptedException { Object obj = new Object(); obj.wait(); }
根据上方wait的功能我们可以分析为何wait和synchronized必须成对使用
wait的作用之一是释放锁,你现在连锁都没有,何来的释放,这不就矛盾了吗,故异常
wait阻塞线程
public static void main(String[] args) { Object obj = new Object(); Thread t1 = new Thread(() -> { synchronized (obj){ System.out.println("wait之前"); try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("wait之后"); } }); t1.start(); }
wait操作后,线程发生阻塞,阻塞在synchronized代码块中,此时释放锁对象obj,这个时候其他线程是可以获取锁对象obj的
关于notify
notify方法也需要和synchronized一起使用(同步方法或者同步代码块)
public static void main(String[] args) { Object obj = new Object(); obj.notify(); }
作用:
1.唤醒现在正在等待(执行了wait方法的线程)了的线程,使他们可以重新尝试获取锁对象
2.如果改锁对象有多个线程都在等待,那么notify方法会随机唤醒一只wait的线程(并没有先来后到啥啥啥的规则顺序)
3.当线程执行notify方法后,并不会立刻改锁对象,而是执行完此线程的所有代码后再释放
wait和notify
1.感受wait和notify
public static void main(String[] args) throws InterruptedException { Object obj = new Object(); Thread t1 = new Thread(() -> { synchronized (obj){ System.out.println("3秒后唤醒obj"); try { Thread.sleep(3000); obj.notify(); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread t2 = new Thread(() -> { synchronized (obj){ System.out.println("wait之前"); try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("wait之后"); } }); t2.start(); t1.start(); }
执行流程:t2先执行拿到obj锁对象,执行wait方法后释放锁对象,线程进入阻塞队列,这个时候t1线程可以获取obj锁对象,休眠3秒后执行notify方法,当t1线程执行完后,释放锁对象,这时候t2线程可以重新获取锁对象,继续执行完下面的代码
理解notify线程执行流程与释放锁的关系
public static void main(String[] args) throws InterruptedException { Object obj = new Object(); Thread t1 = new Thread(() -> { System.out.println("我没在syn代码块内噢"); for(int i = 0; i < 5; i++){ synchronized (obj){ System.out.println("啦啦啦"); obj.notify(); } } }); Thread t2 = new Thread(() -> { synchronized (obj){ System.out.println("wait之前"); try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("wait之后"); } }); t2.start(); t1.start(); }
线程将notify方法执行后,并不是立马释放对象锁,使此线程立即进入阻塞队列
也不是只执行synchronized代码块内的内容,而是将整个线程中的程序全部跑完后再释放锁对象,这是需要注意的
反之,我们也要注意,t1线程中的System.out.println("我没在syn代码块内噢");并没有在synchronized代码块中,所以它是可以执行的,但由于线程调度的不确定性,我们无法确定是t2先执行还是t1先执行故就有下面的两种结果打印,同时也就引出了下面的问题
有没有可能出现先notify再wait的情况?
如上方所说的,由于线程的不确定性调度,我们无法保证一定是先执行wait,后执行notify,那么如果先执行了notify了会有上面影响呢?
答案:这时notify就是一个无效通知 但是也没啥负面影响
故为了先让wait先执行,我们必须要人为加点手段,让代码先执行wait,再执行notify,这时候代码才是有意义的
t2.start(); Thread.sleep(1000); t1.start();
wait死等
在wait没有参数的版本下,如果没有线程执行notify方法,那么执行wait操作的线程就会一直等下去,这并不是一个靠谱的选择,万一哪一天我们忘记写notify了,那不就一直这样白白等着了
wait带参版本:指定了最大等待时间wait(long timeout) 单位:毫秒
public static void main(String[] args) throws InterruptedException { Object obj = new Object(); Thread t = new Thread(() -> { synchronized (obj){ System.out.println("我最多等3秒"); try { obj.wait(3000);//单位是毫秒 } catch (InterruptedException e) { e.printStackTrace(); } } }); long begin = System.currentTimeMillis(); t.start(); t.join(); long end = System.currentTimeMillis(); System.out.println("等待了"+ (end - begin)); }
前面我们说过,当线程在sleep时执行interrupt方法,sleep内部会抛出一个异常,此时会唤醒线程,且程序的后续如何执行由程序的代码决定
而wait带参数的版本遇到sleep时,所发生的情况也与之类似
public static void main(String[] args) throws InterruptedException { Object obj = new Object(); Thread t = new Thread(() -> { synchronized (obj){ System.out.println("我最多等3秒"); try { obj.wait(3000);//单位是毫秒 } catch (InterruptedException e) { e.printStackTrace(); System.out.println("接下里你想干什么,由自己的代码决定"); } } }); t.start(); t.interrupt(); }
notifyAll
notify是随机唤醒一个线程,而notifyAll是唤醒所有等待的线程,一起竞争锁对象