等待唤醒机制

简介: 线程间的`wait`、`notify`及`notifyAll`方法用于同步控制。`wait`使线程释放锁并进入等待状态,直至被`notify`或`notifyAll`唤醒。`notify`随机唤醒一个等待线程,而`notifyAll`唤醒所有等待线程。这些方法需在`synchronized`块内使用,以保证线程安全。正确使用这些方法可避免线程饿死现象,确保线程间有效协作。

由于线程的随机调度,可能会出现“线程饿死”的问题:也就是一个线程加锁执行,然后解锁,其他线程抢不到,一直是这个线程在重复操作

void wait()

当前线程等待,直到被其他线程唤醒

void notify()

随机唤醒单个线程

void notifyAll()

唤醒所有线程

等待(wait):当一个线程执行到某个对象的wait()方法时,它会释放当前持有的锁(如果有的话),并进入等待状态。此时,线程不再参与CPU的调度,直到其他线程调用同一对象的notify()或notifyAll()方法将其唤醒,类似的,wait() 方法也可以传入一个参数表示等待的时间,不加参数就会一直等

唤醒(notify/notifyAll):

notify: 唤醒在该对象监视器上等待的某个线程,如果有多个线程在等待,那么具体唤醒哪一个是随机的

notifyAll: 唤醒在该对象监视器上等待的所有线程

上面的方法是Object提供的方法,所以任意的Object对象都可以调用,下面来演示一下:

public class ThreadDemo14 {
    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        System.out.println("wait前");
        obj.wait();
        System.out.println("wait后");
    }
}

结果抛出了一个异常:非法的锁状态异常,也就是调用wait的时候,当前锁的状态是非法的

这是因为,在wait方法中,会先解锁然后再等待,所以要使用wait,就要先加个锁,阻塞等待就是把自己的锁释放掉再等待,不然一直拿着锁等待,其他线程就没机会了


把wait操作写在synchronized方法里就可以了,运行之后main线程就一直等待中,在jconsole中看到的也是waiting的状态

注意:wait操作进行解锁和阻塞等待是同时执行的(打包原子),如果不是同时执行就可能刚解锁就被其他线程抢占了,然后进行了唤醒操作,这时原来的线程再去等待,已经错过了唤醒操作,就会一直等

wait执行的操作:1. 释放锁并进入阻塞等待,准备接收唤醒通知 2. 收到通知后唤醒,并重新尝试获得锁

接下来再看一下notify方法:

public class ThreadDemo15 {
    private static Object lock = new Object();
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            synchronized (lock){
                System.out.println("t1 wait 前");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("t1 wait 后");
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (lock){
                System.out.println("t2 notify 前");
                Scanner sc = new Scanner(System.in);
                sc.next();//这里的输入主要是构造阻塞
                lock.notify();
                System.out.println("t2 notify 后");
            }
        });
    }
}

然后就会发现又出错了,还是之前的错误,notify也需要先加锁才可以

把之前的notify也加进synchornized就可以了,并且还需要确保是同一把锁

调用wait方法的线程会释放其持有的锁,被唤醒的线程在执行之前,必须重新获取被释放的锁

public class Cook extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                if (Desk.count == 0) {
                    break;
                } else {
                    if (Desk.foodFlag == 0) {
                        try {
                            Desk.lock.wait();//厨师等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        Desk.count--;
                        System.out.println("还能再吃" + Desk.count + "碗");
                        Desk.lock.notifyAll();//唤醒所有线程
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}
public class Foodie extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                if (Desk.count == 0) {
                    break;
                } else {
                    if (Desk.foodFlag == 1) {
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        System.out.println("已经做好了");
                        Desk.foodFlag = 1;
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}
public class Desk {
    public static int foodFlag = 0;
    public static int count = 10;
    //锁对象
    public static Object lock = new Object();
}

这里实现的功能就是,厨师做好食物放在桌子上,美食家开始品尝,如果桌子上没有食物,美食家就等待,有的话,厨师进行等待

sleep() 和 wait() 的区别:

这两个方法看起来都是让线程等待,但是是有本质区别的,使用wait的目的是为了提前唤醒,sleep就是固定时间的阻塞,不涉及唤醒,虽然之前说的Interrupt可以使sleep提前醒来,但是Interrupt是终止线程,并不是唤醒,wait必须和锁一起使用,wait会先释放锁再等待,sleep和锁无关,不加锁sleep可以正常使用,加上锁sleep不会释放锁,抱着锁一起睡,其他线程无法拿到锁

在刚开始提到过,如果有多个线程都在同一个对象上wait,那么唤醒哪一个线程是随机的:

public class ThreadDemo16 {
    private static Object lock = new Object();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("t1 wait 前");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("t1 wait 后");
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("t2 wait 前");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("t2 wait 后");
            }
        });
        Thread t3 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("t3 wait 前");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("t wait 后");
            }
        });
        Thread t4 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("t4 notify 前");
                Scanner sc = new Scanner(System.in);
                sc.next();
                lock.notify();
                System.out.println("t4 notify 后");
            }
        });
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

这次只是t1被唤醒了

还可以使用notifyAll,把全部的线程都唤醒

相关文章
|
4月前
|
消息中间件 Python
线程通信
【7月更文挑战第1天】
33 2
|
4月前
|
Java
线程间通信的几种方法
线程间通信的几种方法
|
4月前
|
Java 开发者
线程通信的方法和实现技巧详解
线程通信的方法和实现技巧详解
|
6月前
|
安全 Java 数据库连接
详细介绍线程间通信
详细介绍线程间通信 线程间通信是指在多线程编程中,不同的线程之间通过某种方式交换信息的过程。这是一个重要的概念,因为线程之间的协作是实现复杂并发系统的关键。 下面是一些线程间通信的常见方式和示例:
1148 0
|
6月前
线程间的通信
线程间的通信
33 0
|
6月前
|
Java
并发编程-线程等待唤醒机制
并发编程-线程等待唤醒机制
|
6月前
|
消息中间件 Linux
线程间通信
线程间通信 在并发编程中,线程之间的数据共享和信息传递是非常普遍的需求。如果没有线程间通信,多线程编程将变得毫无意义,因为这些线程无法相互协调和合作,无法完成更复杂的任务。因此,线程间通信是多线程编程中最重要的概念之一。
|
监控
线程通信
线程通信
58 0
高并发编程-使用wait和notifyAll进行线程间的通信3_多线程下的生产者消费者模型和notifyAll
高并发编程-使用wait和notifyAll进行线程间的通信3_多线程下的生产者消费者模型和notifyAll
68 0