由于操作系统对线程的调度是随机执行的,且线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知。但是,有时候在实际开发中,我们希望合理的协调多个线程之间的先后执行顺序。在Java中,wait()方法和notify()方法就是解决该问题的。

1.wait()方法

线程饿死:线程饿死也叫线程饥饿,当多个线程同时竞争一把锁的时候,由于操作系统对线程的调度是随机的,所以当获取锁的线程释放锁之后,接下来哪个线程会拿到锁,这是不确定的。但是由于其他线程都属于在锁上阻塞等待,处于阻塞状态,而当前释放锁的线程处于就绪状态,这个线程还是有很大概率拿到该锁的。这样就会导致其他线程一直吃不到CPU资源,出现线程饿死的现象。

语法形式:

syncronized(锁对象){

       锁对象.wait();

}

虽然我们无法干预调度器对于线程的调度,但是我们可以通过调用wait()方法,然后面的逻辑先执行,等后面的逻辑先执行完之后,当前面的逻辑收到通知,再继续执行。

wait()所作的事情

1.让调用wait方法的线程进行等待(将该线程放到等待队列中)

2.释放当前线程所持有的锁

3.满足一定条件被唤醒之后,线程再此重新获取该锁

当一个线程调用对象的wait方法时,该线程会释放该对象的监视器锁,并进入等待队列中等待被唤醒。

 

注意:由于使用wait()方法会释放锁,所以,再Java中,使用notify()方法之前,我们先加锁,要搭配syncronized使用。否则会报出一个非法锁状态的异常。

如下图

wait()和join()的区别

两个方法都是等,但是join方法必须要等待另一个线程全部执行完之后,代码才能继续走下去,但是wait方法不一定另一个线程全部执行完,只需要下面的逻辑执行到notify方法,代码就可以继续走下去。

syncronized不也是等待吗?

我们要知道,有时尽管写了syncronized,但是它不一定触发等待,因为我们不确定别的线程是否为加锁状态。如果其他线程没有处于加锁状态,那么该线程就直接获取到锁,直接加锁,这个过程中并没有等待。 

wait方法的结束条件:

1.其他线程调用notify方法

2.其他线程调用该线程的interrupted方法,会导致wait抛出InterruptedException异常,导致wait被唤醒,同时也导致该线程结束。

3.wait方法也提供了带参数版本,来指定等待的时间。这时只要等待的时间到了,线程就会自动唤醒,不用notify来通知唤醒。

当代码执行到wait方法后,该线程会一直等待下去,那么我们肯定不能让该线程继续等待下去,这时,我们就要用到notify方法去唤醒线程了。 

2.notify()方法

语法:

syncronized(锁对象){

      锁对象.notify();

}

notify()方法是用来唤醒因为wait方法而阻塞的线程。

注意事项:

1. notify()方法也要搭配syncronized()使用,这是Java中特殊规定的。

2. 使用notify()之前,务必要确保先wait了,否则,notify()方法就没起到唤醒的作用,但是也不会有副作用(抛异常)

3. 如果有多个线程在同一个锁对象上进行了wait,那么,notify()会随机唤醒多个线程中的一个线程。

4. 代码执行到notify()方法后,当前线程并不会马上释放锁,而是要等到执行完notify()方法的线程将程序执行完之后才会释放锁,也就是退出同步代码块(syncronied修饰的代码块)之后才会释放对象锁。

5. wait方法和notify方法锁对象要是一个锁对象,才会起作用

6. 被唤醒的线程不会立即获得对象的监视器锁并继续执行,而是从等待队列中移出,进入与其他线程竞争锁的状态。只有当该线程获得了对象的监视器锁后,才能继续执行wait之后的代码。

例子:

public class Demo9 {
    public static void main(String[] args) {
        Object locker=new Object();
        Thread t1=new Thread(()->{
            synchronized (locker){
                System.out.println("t1在wait之前");
                try {
                    System.out.println("t1执行到wait,释放锁");
                    locker.wait();
                    System.out.println("t1被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t2=new Thread(()->{
            synchronized (locker){
                System.out.println("t2获取到锁");
                try {
                    locker.wait();
                    System.out.println("t2被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t3=new Thread(()->{
            synchronized (locker){
                System.out.println("t3唤醒t1");
                locker.notify();
                System.out.println("同步代码块执行完,t3释放锁");
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

根据代码运行结果分析代码逻辑:线程t1启动并获取到锁,进行加锁,接着线程t2也启动,也尝试获取锁,由于t1没有释放锁,t2就阻塞了。接着在线程t1中,执行到wait()方法,t1此时就释放锁,然后线程t3就获取到了锁,当在线程t3中执行到notify()方法,去唤醒t1,当t3中的同步代码块里面的逻辑执行完后,就释放锁 ,由于t1被唤醒需要时间在这段时间内,由于t1没被唤醒,无法获取到锁,线程t2就获取到了锁,线程t2就会执行到wait,wait方法就会导致线程t2释放锁,这回t1就已经被唤醒了,就会获取到锁,线程t1就会继续执行下去。但是线程t2由于没有notify方法去唤醒,所以它就一直处于睡眠状态,不会被唤醒。

2.1notifyAll()

如果我们想要一次唤醒多个线程在同一个锁对象进行wait,我们就可以使用notifyAll()方法。

如修改上面代码,将线程t1和线程t2全部唤醒。

    public static void main(String[] args) {
        Object locker=new Object();
        Thread t1=new Thread(()->{
            synchronized (locker){
                System.out.println("t1在wait之前");
                try {
                    locker.wait();
                    System.out.println("t1被notifyAll()唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t2=new Thread(()->{
            synchronized (locker){
                System.out.println("t2在wait之前");
                try {
                    locker.wait();
                    System.out.println("t2被notifyAll()唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t3=new Thread(()->{
            Scanner scanner=new Scanner(System.in);
            System.out.println("请输入唤醒所有线程");
            scanner.next();
            synchronized (locker){
                locker.notifyAll();
                System.out.println("notifyall方法后");
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }

注意事项:notifyAll()方法在唤醒所有在等待队列中的线程时,这几个线程是存在锁竞争的,只有一个线程能获得锁,其余线程则会继续阻塞,继续尝试获取锁。 

 3.wait()和sleep()的区别

1.wait方法需要搭配锁来使用,先加锁,之后才能wait,而sleep使用前不需要加锁。

2.如果都是在syncronized内部使用,wait会释放锁,而sleep方法不会释放锁。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部