线程等待与唤醒
目录
概念
- 在我们多线程开发的时候,有时候会有让一些线程先执行的,这些线程结束后,其他线程再继续执行,列如我们生活中打球,球场的每个人都是一个线程,那一个球员必须先传球给另一个球员,那么另一个球员才能投篮,这就是一个线程的一个动作执行完,另一个线程的动作才能执行
- 线程间的等待与唤醒机制,wait和notify是Object的方法,用于线程的等待与唤醒,必须搭配着synchronized来使用,脱离 synchronized 使用 wait 会直接抛出异常
等待方法
- 痴汉方法,死等,线程进入阻塞态(WAITING),直到有其他线程调用notify方法唤醒
- 等待一段时间,若再该时间内线程被唤醒,则继续执行,若超过了相应时间还没有其他线程唤醒此线程,此线程不再等待,恢复执行
等待方法做的事
- 调用wait方法的前提是获得这个对象的锁(synchronized对象锁,如果有多个线程取竞争这个锁,只有一个线程获得锁,其他线程会处于等待队列)
使当前执行代码的线程进行等待 . ( 把线程放到等待队列中 )- 调用wait方法会释放锁
- 满足一定条件会重新尝试获得这个锁,被唤醒的之后不是立即恢复执行,而是进入阻塞队列,竞争锁
结束等待的三个方式
- 其他线程调用该对象的 notify 方法.
- wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
- 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常
唤醒方法
- notify()随机唤醒一个处在等待状态的线程
- notifyAll()唤醒所有处在等待状态的线程
- 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的 其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
- 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 "先来后到") 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
唤醒和阻塞具体的使用
package thread.wait_notify; public class waitDemo { private static class WaitTask implements Runnable{ private Object lock; public WaitTask(Object lock){ this.lock=lock; } @Override public void run() { synchronized (lock){ System.out.println(Thread.currentThread().getName()+ "准备进入等待状态"); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "等待结束,线程继续执行"); } } } private static class Notify implements Runnable{ private Object lock; public Notify(Object lock){ this.lock=lock; } @Override public void run() { synchronized (lock){ System.out.println("准备唤醒等待线程"); //随机唤醒一个等待线程 lock.notify(); System.out.println("唤醒结束"); } } } public static void main(String[] args) throws InterruptedException { Object lock=new Object(); Thread t1=new Thread(new WaitTask(lock),"t1"); Thread t2=new Thread(new WaitTask(lock),"t2"); Thread t3=new Thread(new WaitTask(lock),"t3"); Thread notify=new Thread(new Notify(lock),"notify"); t1.start(); t2.start(); t3.start(); Thread.sleep(100); notify.start(); } }
注意点
必须搭配synchroized使用,不然会直接报错
背后工作原理解析
- 如果是调用notifyAll,就会将这三个线程都放入阻塞队列,然后进行竞争锁资源
- 一定要明确锁的资源是谁,引起的竞争的必须是线程调用的锁的对象一定是要一样的,如果竞争的不是同一个锁,那么就不会进入同一个阻塞队列
- 其只有唤醒线程执行完毕,才会有阻塞队列线程的执行
- 阻塞队列怎么理解,比如当前t1线程获得了锁资源,那么t2,t3如果想竞争这个锁,就得处于阻塞队列,当t1线程调用了wait方法,释放了锁资源,那么t2和t3就会去竞争锁资源,然后其中获得一个,依次类推,当三个线程都处于等待队列,当调用了notify线程,等待队列其中一个进入阻塞队列,但是阻塞队列就算只有一个线程,也不会立即得到锁,因为notify线程也会占用锁,必须等notify线程结束,释放锁
wait和sleep的区别
- 其实理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间, 唯一的相同点就是都可以让线程放弃执行一段时间
- 如果有共性就先介绍共性,如果没有,分别介绍即可
- wait方法是Object类提供的方法,需要搭配synchroized锁来使用,调用wait方法会释放锁,等待线程会被其他线程唤醒或者超时自动唤醒,唤醒之后需要再次竞争synchronized锁才能继续执行
- sleep是Thread类提供的方法(不一定要搭配synchronized使用),调用sleep方法进入TIMED_WAITING状态,如果占用锁也不会不会释放锁,时间到了自动唤醒
为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里
- 因为Java所有类的都继承了Object,Java想让任何对象都可以作为锁,并且 wait(),notify()等方法用于等待对象的锁或者唤醒线程,在 Java 的线程中并没有可供任何对象使用的锁,所以任意对象调用方法一定定义在Object类中。
为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调
- 当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态(等待队列)直到其他线程调用这个对象上的 notify()方法。同样的,当一个线程需要调用对象的 notify()方法时,它会释放这个对象的锁(在执行完锁的代码内容),以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。







