wait,notify,notifyAll,sleep,join等线程方法的全方位演练

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

一、概念解释

1. 进入阻塞

有时我们想让一个线程或多个线程暂时去休息一下可以使用 wait()使线程进入到阻塞状态等到后面用到它时再使用notify()、notifyAll() 唤醒它线程被唤醒后会等待CPU调度。不过需要注意的是在执行 wait() 方法前必须先拿到这个对象的monitor锁。

2. 线程阻塞后通常有以下四种方式唤醒

  • 另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程

  • 另一个线程调用这个对象的notify()方法

  • 过了waiting timeout()规定的超时时间如果传入()就是永久等待

  • 线程自身调用了interrupt()

3. notify 与 notifyAll

  • notify()唤醒单个正在等待monitor锁的线程如果有多个正在等待monitor的线程只会选取一个唤醒具体唤醒那一个是任意的不确定的Java对此并没有一个严格的规范JVM内部可以拥有自己的实现。
  • notifyAll()一次性把所有等待的线程唤醒至于哪一个会获得monitor锁取决于cpu调度。

4. Monitor监视器锁原理

任何一个对象都有一个Monitor与之关联当且一个Monitor被持有后它将处于锁定状态。Synchronized在JVM里的实现都是 基于进入和退出Monitor对象来实现方法同步代码块同步

5. wait() 遇到 interrupt() 时

假设线程已经执行了 wait() 方法那么在此期间如果被中断了它会和之前一样抛出InterruptException并且释放掉目前已获得的monitor。

二、wait()和notify()、notifyAll()

1. wait()和notify()的基本用法

/**
 * 描述     展示wait和notify的基本用法 1. 研究代码执行顺序 2. 证明wait释放锁
 */
public class Wait {

    public static Object object = new Object();

    static class Thread1 extends Thread {

        @Override
        public void run() {
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + "开始执行了");
                try {
                    object.wait(); //进入阻塞状态会释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁。");
            }
        }
    }

    static class Thread2 extends Thread {

        @Override
        public void run() {
            synchronized (object) {
                object.notify(); //唤醒正在等待这把锁的单个线程
                System.out.println("线程" + Thread.currentThread().getName() + "调用了notify()");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();
        thread1.start();
        Thread.sleep(200);
        thread2.start();
    }
}

打印结果

上面的代码表示线程 A 在使用 wait() 方法后会进入阻塞状态并释放object的锁然后另一个线程 B 会获取到该object的锁在执行 notify() 方法后会唤醒等待这把锁的 线程 A 然后线程 A 继续执行!

2. notify()和notifyAll()的基本用法

(1)notifyAll()唤醒在等待某把锁的全部线程

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 描述     3个线程线程1和线程2首先被阻塞线程3唤醒它们。notify, notifyAll。 start先执行不代表线程先启动。
 */
public class WaitNotifyAll implements Runnable {

    private static final Object resourceA = new Object();


    public static void main(String[] args) throws InterruptedException {
        Runnable r = new WaitNotifyAll();
        Thread threadA = new Thread(r);
        Thread threadB = new Thread(r);
        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
                    resourceA.notifyAll();
//                    resourceA.notify();
                    System.out.println("ThreadC notified.");
                }
            }
        });
        threadA.start();
        threadB.start();
        Thread.sleep(200);
        threadC.start();
    }
    @Override
    public void run() {
        synchronized (resourceA) {
            System.out.println(Thread.currentThread().getName()+" got resourceA lock.");
            try {
                System.out.println(Thread.currentThread().getName()+" waits to start.");
                resourceA.wait();
                System.out.println(Thread.currentThread().getName()+"'s waiting to end.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

使用notifyAll()的打印结果:

线程1和线程2首先调用 wait() 进入阻塞线程3使用 notifyAll 唤醒它们然后线程1和线程2都会被唤醒继续执行但是不能保证这两个线程哪个先执行。

2notify()唤醒在等待某把锁的一个线程

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 描述     3个线程线程1和线程2首先被阻塞线程3唤醒它们。notify, notifyAll。 start先执行不代表线程先启动。
 */
public class WaitNotifyAll implements Runnable {

    private static final Object resourceA = new Object();


    public static void main(String[] args) throws InterruptedException {
        Runnable r = new WaitNotifyAll();
        Thread threadA = new Thread(r);
        Thread threadB = new Thread(r);
        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
//                    resourceA.notifyAll();
                    resourceA.notify();
                    System.out.println("ThreadC notified.");
                }
            }
        });
        threadA.start();
        threadB.start();
        Thread.sleep(200);
        threadC.start();
    }
    @Override
    public void run() {
        synchronized (resourceA) {
            System.out.println(Thread.currentThread().getName()+" got resourceA lock.");
            try {
                System.out.println(Thread.currentThread().getName()+" waits to start.");
                resourceA.wait();
                System.out.println(Thread.currentThread().getName()+"'s waiting to end.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

使用notify()打印结果

由于是调用的 notify() 方法所以只会唤醒等待这把锁的其中一个线程但是具体会唤醒哪一个也不确定。

3. wait只释放当前调用者对象的那把锁

执行wait方法一定是一个Object对象对象和 Monitor 监视器锁绑定一个对象执行wait()就会释放掉该对象的锁不会影响到其他对象的锁每个对象的锁之间是独立的。

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 描述     证明wait只释放当前的那把锁
 */
public class WaitNotifyReleaseOwnMonitor {

    private static volatile Object resourceA = new Object();
    private static volatile Object resourceB = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
                    System.out.println("ThreadA got resourceA lock.");
                    synchronized (resourceB) {
                        System.out.println("ThreadA got resourceB lock.");
                        try {
                            System.out.println("ThreadA releases resourceA lock.");
                            resourceA.wait();

                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resourceA) {
                    System.out.println("ThreadB got resourceA lock.");
                    System.out.println("ThreadB tries to resourceB lock.");

                    synchronized (resourceB) {
                        System.out.println("ThreadB got resourceB lock.");
                    }
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

打印结果

resourceB 的锁一直在 ThreadA手中没有被释放所以线程ThreadB会一直等待中。

4. wait()、notify()和notifyAll() 引起的线程状态的特殊转换

六种状态的含义和相互转换

上图是《线程的六种状态》一文中线程的六种状态之间的正常转换轨迹但是 wait()/notify()、notifyAll() 方法会导致状态之间的特殊转换

比如线程A从 Object.wait() 状态刚被唤醒时通常不能立刻抢到 monitor 锁会先等待 CPU 调度这时的状态转换是由 Waiting 进入Blocked状态等抢到锁后再转换到Runnable状态但是 wait() 时如果发生异常会直接跳到终止Terminated状态即从Waiting直接到Terminated。

三、sleep方法详解

作用只想让线程在预期的时间执行其他时间不要占用CPU资源

特点sleep方法可以让线程进入Timed_Waiting状态并且不占用CPU资源但是不释放锁包括synchronize和lock直到规定时间后再执行休眠期间如果被中断会抛出异常并清除中断标志。

1. sleep不释放锁

1 sleep不释放synchronized的Monitor

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 展示线程sleep的时候不释放synchronized的monitor等sleep时间到了以后正常结束后才释放锁
 */
public class SleepDontReleaseMonitor implements Runnable {

    public static void main(String[] args) {
        SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
        new Thread(sleepDontReleaseMonitor).start();
        new Thread(sleepDontReleaseMonitor).start();
    }

    @Override
    public void run() {
        syn();
    }

    private synchronized void syn() {
        System.out.println("线程" + Thread.currentThread().getName() + "获取到了monitor。");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + "退出了同步代码块");
    }
}


执行结果

sleep() 方法不会释放锁等到sleep的指定时间一过会继续执行然后该线程的全部代码执行结束才会释放锁其他线程继续执行。

2sleep不释放lock

package threadcoreknowledge.threadobjectclasscommonmethods;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 描述     演示sleep不释放locklock需要手动释放
 */
public class SleepDontReleaseLock implements Runnable {

    private static final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        lock.lock();
        System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁");
        try {
            Thread.sleep(5000);
            System.out.println("线程" + Thread.currentThread().getName() + "已经苏醒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
        new Thread(sleepDontReleaseLock).start();
        new Thread(sleepDontReleaseLock).start();
    }
}


执行结果

线程1在获取到锁之后进入sleep()休眠中但是并没有释放锁直到 lock.unlock() 之后所才会被释放其他线程继续执行

2. sleep响应中断

sleep() 有两种写法TimeUnit.SECONDS.sleep(1) 、Thread.sleep(1000);

package threadcoreknowledge.threadobjectclasscommonmethods;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 描述     每个1秒钟输出当前时间被中断观察。
 * Thread.sleep()
 * TimeUnit.SECONDS.sleep() 方便开发人员对时间的把控不需要通过毫秒换算
 */
public class SleepInterrupted implements Runnable{

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new SleepInterrupted());
        thread.start();
        Thread.sleep(6500);
        thread.interrupt();
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(new Date());
            try {
//                TimeUnit.HOURS.sleep(3);
//                TimeUnit.MINUTES.sleep(25);
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                System.out.println("我被中断了");
                e.printStackTrace();
            }
        }
    }
}


执行结果

在 sleep() 期间如果当前线程调用了 interrupt() 方法会抛出 InterruptedException 异常来响应中断

四、join()

作用因为新的线程加入了我们所以我们要等他执行完再出发

用法主线程等待执行 join() 方法加入的子线程

基于 join() 封装的工具类CountDownLatch 或 CyclicBarrier 类

join 期间主线程处于什么状态WAITING 状态

代码演示演示 join注意语句输出顺序会变化。

public class Join {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "开始执行");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "执行完毕");
        });
        Thread thread2 = new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "开始执行");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "执行完毕");
        });

        thread.start();
        thread2.start();
        System.out.println("开始等待子线程运行完毕");
        thread.join();
        thread2.join();
        System.out.println("所有子线程执行完毕");
    }
}

打印结果

由于使用了join() 方法主线程会等到子线程都执行完毕才会继续执行 System.out.println(“所有子线程执行完毕”);

        thread.start();
        thread2.start();
        System.out.println("开始等待子线程运行完毕");
        // thread.join();
        // thread2.join();
        System.out.println("所有子线程执行完毕");

如果注释掉 join() 方法则主线程会先打印出“所有子线程执行完毕”之后子线程会继续执行并陆续打印。如下所示

2. join遇到中断

/******
 *  Join的中断演示
 */
public class JoinInterrupted {
    public static void main(String[] args) {
        Thread mainThread = Thread.currentThread();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //中断主线程
                    mainThread.interrupt();
                    Thread.sleep(5000);
                    System.out.println("Thread1 sleep 结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("子线程执行完毕");
            }
        });

        thread1.start();
        System.out.println("等待子线程运行完毕");
        try {
            thread1.join();
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+"主线程被中断");
            e.printStackTrace();
        }
        System.out.println("主线程等待子线程执行完毕");
    }
}

主线程被 interrupt 中断后直接跳到最后一句 System.out.println(“主线程等待子线程执行完毕”) 但是子线程依然在运行最后打印如下

明显主线程运行被中断之后子线程依然在继续执行“主线程等待子线程执行完毕” 在 “子线程执行完毕” 之前打印join() 的效果失效。如果要保证 join () 期间被中断时主线程和子线程的一致性也需要也将子线程中断掉这就 需要在 catch 到 InterruptedException之后也要将中断传给子线程

public class JoinInterrupted {
    public static void main(String[] args) {
        Thread mainThread = Thread.currentThread();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //中断 主线程
                    mainThread.interrupt();
                    Thread.sleep(5000);
                    System.out.println("Thread1 执行完毕");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("子线程中断");
                }
            }
        });

        thread1.start();
        System.out.println("等待子线程运行完毕");
        try {
            thread1.join();
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+"主线程被中断");
            e.printStackTrace();
            //中断 子线程
            thread1.interrupt();
        }
        System.out.println("子线程运行完毕");
    }
}

打印结果

主线程和子线程的 catch 中的逻辑是并行执行的所以不能保证哪个最先停止运行。

3.在 join()期间线程是什么状态

主线程是 WAITING 状态而调用 join() 方法的子线程是 Runnable 状态。

代码演示

/******
 *  先join再mainThread.getState();
 *  通过debug看线程状态
 */
public class JoinThreadState {
    public static void main(String[] args) throws InterruptedException {
        Thread mainThread = Thread.currentThread();

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    //查看主线程状态
                    System.out.println(mainThread.getState());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread.start();
        System.out.println("子线程启动");
        thread.join();
        System.out.println("子线程运行完毕");
    }
}

打印结果

3. join 源码

如果调用了wait()那么他不应该被notify()唤醒吗但是没有在join()方法里面看到notify

  • Thread类是 Object的一个特殊的子类每个Thread实例运行完毕之后都会执行一次notify()的操作

从 join() 的源码可以看出thread.join() 相当于以下代码

//等价代码实现join()
synchronized (thread){
    thread.wait();
}

五、yield

作用释放当前线程的 CPU 时间片让当前处于运行状态的线程退回到可运行状态让出抢占资源的机会但是它的状态依然是 Runnable。

定位JVM 不保证遵循yield的规则如果cpu资源充足的话可能yield不生效。和 join 相反join 是别人插队进来了yield 是线程让出位置。

yield 和 sleep 区别yield只是暂时让出cpu由于当前线程还是 Runnable并没有阻塞所以随时可能再次被调度sleep期间线程调度器认为它已经被阻塞了不会去调度它。

六、课后测验

1. 为什么wait/notify/notifyAll被定义在Object类中,而sleep定义在Thread类中

因为java中每个对象都有一把称之为monitor监控器的锁每个对象头中有一个用来保存锁信息的位置所以每个对象都可以上锁这个锁是对象级别的而非线程级别的wait/notify/notifyAll也都是锁级别的操作他们的锁属于对象所以把他们定义在Object类中最合适因为Object类是所有对象的父类。

而如果把 wait/notify/notifyAll 方法定义在Thread类中会带来很大的局限性比如一个线程可能持有多把锁以便实现相互配合的复杂逻辑假设此时wait方法定义到Thread类中如何实现让一个线程持有多把锁呢又如何明确线程等待的是那把锁呢既然我们是让当前线程去等待某个对象的锁自然应该通过操作对象来实现而不是操作线程。

对于sleep为什么被定义在Thread中我们只要从sleep方法的作用来看就知道了sleep的作用是让线程在预期的时间内执行其他时候不要来占用CPU资源。从上面的话术中便可以理解为sleep是属于线程级别的它是为了让线程在限定的时间后去执行由线程控制。

2. wait/notify和sleep方法的异同

相同点
1.他们都可以让线程阻塞
2.它们都可以响应interrupt中断在等待的过程中如果收到中断信号都可以进行响应
并抛出InterruptedException
不同点
1.wait方法必须在synchronized保护的代码中使用而sleep方法并没有这个要求
2.在同步代码中执行sleep方法时并不会释放monitor锁但执行wait方法时会主动释放monitor锁
3.sleep方法中要求必须定义一个时间时间到期后会主动恢复而对于没有time参数的 wait() 方法而言意味着永久等待直到被中断或者唤醒才能恢复他并不会主动恢复.
4.wait/notify是Object方法而sleep是Thread类的方法

3. wait方法是属于Object对象的那调用Thread.wait会怎么样

Thread也是个对象这样调用也没有问题但是Thread是个特殊的对象线程退出的时候会自动执行notify这样会是我们设计的流程受到干扰所以我们一般不这么用。

4. 代码练习wait/notify 实现生产者消费者模式

1为什么要使用生产者和消费者模式

在线程的世界中生产者就是生产一些数据而消费者就是把这些数据消费使用但是他们的速度很可能就是不一致的有的时候是生产者快有的时候生产者慢而消费者快就需要有个设计模式去解决这个问题而不至于一个过快一个过慢于是就诞生了生产者消费者模式这个设计模式实际上把生产方和消费方进行了解耦从而达到更加流畅的配合。

2能解决什么问题

能解决生产过快消费不足或者生产不足消费过快的问题而且能让生产方和消费方之间解耦。

3代码演示

定义一个数据容器 用于存储生产出的数据并定义该容器的两个方法生产数据的put方法和消费数据的take方法。

put方法逻辑在方法中使用 synchronized 代码块加同步锁防止出现线程安全问题。在 synchronized 代码块中如果storage队列满了就调用 wait() 等待不再生产数据。代码块的末尾加上notify()调用表示每次 put 数据都要唤醒一下消费者端的线程。

take方法逻辑同样使用 synchronized 代码块在代码块中如果队列为空就调用 wait() 等待末尾也同样加上notify()调用表示每次消费都唤醒一下生产端的线程。

为什么使用notify()调用

  • 如果生产者生产过快时storage会处于满的状态这时候不再生产数据处于等待状态消费者那边会消费数据消费完最后会调用notify方法唤醒生产者继续生产数据。
  • 如果消费者消费过快时storage会处于空的状态这时候消费者这边检查到没有数据消费就处于等待状态生产者那边生产出一条数据后会唤醒消费者继续消费数据。
// 数据容器:用于存储生产出的数据并定义该容器的两个方法生产数据的put方法和消费数据的take方法。
class EventStorage {

    private int maxSize;
    private LinkedList<Date> storage;

    public EventStorage() {
        maxSize = 10;
        storage = new LinkedList<>();
    }

    //生产产品
    public synchronized void put() {
        //如果满了
        while (storage.size() == maxSize) {
            try {
               wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没满
        storage.add(new Date());
        System.out.println("仓库里有了" + storage.size() + "个产品。");
        // 每次生产都执行一次唤醒
        notify();
    }
    
    //消费产品
    public synchronized void take() {
        //如果空了
        while (storage.size() == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没空
        System.out.println("拿到了" + storage.poll() + "现在仓库还剩下" + storage.size());
        // 每次消费都执行一次唤醒
        notify();
    }
}
// 生产者类 用于生产数据可以看到生产者中storage用于存储生产出的数据run方法用于完成生产数据的任务。
class Producer implements Runnable {

    private EventStorage storage;

    public Producer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.put();
        }
    }
}
// 消费者类 用于消费数据
class Consumer implements Runnable {

    private EventStorage storage;

    public Consumer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.take();
        }
    }
}
// 主类 启动一个线程生产数据另一个线程消费数据。
public class ProducerConsumerModel {

    public static void main(String[] args) {
        EventStorage eventStorage = new EventStorage();
        Producer producer = new Producer(eventStorage);
        Consumer consumer = new Consumer(eventStorage);
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}

打印结果

5. 代码练习实现两个线程交替打印 0~100 的奇偶数

问题描述有两个线程一个线程只打印奇数一个线程只打印偶数而且要按照顺序打印就是说要按照这样的方式打印

偶线程0
奇线程1
偶线程2
奇线程3

1使用synchronized关键字实现

分析

  • 创建两个线程一个线程处理偶数一个线程处理奇数
  • 两个线程之间通过synchronized进行同步保证count++每次只有一个线程进行操作
  • 为什么两个线程能交替执行这里很巧的是count从0123自增过程就是一个奇偶数交替的过程实际上两个线程都是在不停的尝试while循环进入synchronized代码块如果满足相对应的条件偶数或是奇数就打印输出。

代码展示

public class WaitNotifyPrintOddEvenSyn {

    private static int count;

    private static final Object lock = new Object();

    /**
     * 新建2个线程第一个只处理偶数第二个只处理奇数用位运算用synchronized来通信
     */
    public static void main(String[] args) {
        new Thread(() -> {
            while (count < 100) {
                synchronized (lock) {
                    if ((count & 1) == 0) {
                        System.out.println(Thread.currentThread().getName() + ":" + count++);
                    }
                }
            }
        }, "偶线程").start();

        new Thread(() -> {
            while (count < 100) {
                synchronized (lock) {
                    if ((count & 1) == 1) {
                        System.out.println(Thread.currentThread().getName() + ":" + count++);
                    }
                }
            }
        }, "奇线程").start();
    }
}

打印结果

(2) 方案二使用wait/notify关键字推荐

分析

  • 偶数线程拿到锁打印输出同时count++然后进行休眠因为wait()方法的特性休眠的同时会释放monitor锁奇数线程就可以进来了进来后打印输出同时notify唤醒偶数线程继续下一轮奇数线程往下执行wait方法休眠就这样偶数线程唤醒奇数线程奇数线程唤醒偶数线程直到满足count<100条件后线程不再休眠直接退出程序。
  • 这个要点一个在于wait/notify的等待唤醒机制一个在于wait()方法的特性休眠后会释放锁。
  • 这种方式和上面那种方式不同点在于这种方式是被动唤醒的机制而上面那个是线程不断重试的机制一直while重试直到满足条件就打印有些浪费资源很明显这种方式优于上面那种
public class WaitNotifyPrintOddEveWait {

    private static int count = 0;

    private static final Object lock = new Object();

    public static void main(String[] args) {
        new Thread(new TurningRunner(), "偶线程").start();
        new Thread(new TurningRunner(), "奇线程").start();
    }

    /**
     * 1. 拿到锁立刻打印
     * 2. 打印完唤醒其他线程自己就休眠
     */
    static class TurningRunner implements Runnable {

        @Override
        public void run() {
            while (count < 100) {
                synchronized (lock) {
                    //拿到锁就打印
                    System.out.println(Thread.currentThread().getName() + ":" + count++);
                    lock.notify();
                    if (count < 100) {
                        try {
                            //如果任务还没结束就让出当前的锁并休眠
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

文章来源waitnotifynotifyAllsleepjoin等线程方法的全方位演练

个人微信CaiBaoDeCai

微信公众号名称Java知者

微信公众号 ID JavaZhiZhe

谢谢关注

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6