线程等待与唤醒

目录

概念

等待方法 

唤醒方法 

唤醒和阻塞具体的使用 

wait和sleep的区别


概念

  • 在我们多线程开发的时候,有时候会有让一些线程先执行的,这些线程结束后,其他线程再继续执行,列如我们生活中打球,球场的每个人都是一个线程,那一个球员必须先传球给另一个球员,那么另一个球员才能投篮,这就是一个线程的一个动作执行完,另一个线程的动作才能执行
  • 线程间的等待与唤醒机制,wait和notify是Object的方法,用于线程的等待与唤醒,必须搭配着synchronized来使用,脱离 synchronized 使用 wait 会直接抛出异常

等待方法 

  • 痴汉方法,死等,线程进入阻塞态(WAITING),直到有其他线程调用notify方法唤醒

  • 等待一段时间,若再该时间内线程被唤醒,则继续执行,若超过了相应时间还没有其他线程唤醒此线程,此线程不再等待,恢复执行

 等待方法做的事

  1. 调用wait方法的前提是获得这个对象的锁(synchronized对象锁,如果有多个线程取竞争这个锁,只有一个线程获得锁,其他线程会处于等待队列)
  2. 使当前执行代码的线程进行等待 . ( 把线程放到等待队列中 )
  3. 调用wait方法会释放锁
  4. 满足一定条件会重新尝试获得这个锁,被唤醒的之后不是立即恢复执行,而是进入阻塞队列,竞争锁

结束等待的三个方式

  1. 其他线程调用该对象的 notify 方法.
  2. wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
  3. 其他线程调用该等待线程的 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()方法时,它会释放这个对象的锁(在执行完锁的代码内容),以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。