一、概念解释

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()的基本用法

  1. /**
  2. * 描述: 展示wait和notify的基本用法 1. 研究代码执行顺序 2. 证明wait释放锁
  3. */
  4. public class Wait {
  5. public static Object object = new Object();
  6. static class Thread1 extends Thread {
  7. @Override
  8. public void run() {
  9. synchronized (object) {
  10. System.out.println(Thread.currentThread().getName() + "开始执行了");
  11. try {
  12. object.wait(); //进入阻塞状态会释放锁
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁。");
  17. }
  18. }
  19. }
  20. static class Thread2 extends Thread {
  21. @Override
  22. public void run() {
  23. synchronized (object) {
  24. object.notify(); //唤醒正在等待这把锁的单个线程
  25. System.out.println("线程" + Thread.currentThread().getName() + "调用了notify()");
  26. }
  27. }
  28. }
  29. public static void main(String[] args) throws InterruptedException {
  30. Thread1 thread1 = new Thread1();
  31. Thread2 thread2 = new Thread2();
  32. thread1.start();
  33. Thread.sleep(200);
  34. thread2.start();
  35. }
  36. }

打印结果

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

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

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

  1. package threadcoreknowledge.threadobjectclasscommonmethods;
  2. /**
  3. * 描述: 3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。notify, notifyAll。 start先执行不代表线程先启动。
  4. */
  5. public class WaitNotifyAll implements Runnable {
  6. private static final Object resourceA = new Object();
  7. public static void main(String[] args) throws InterruptedException {
  8. Runnable r = new WaitNotifyAll();
  9. Thread threadA = new Thread(r);
  10. Thread threadB = new Thread(r);
  11. Thread threadC = new Thread(new Runnable() {
  12. @Override
  13. public void run() {
  14. synchronized (resourceA) {
  15. resourceA.notifyAll();
  16. // resourceA.notify();
  17. System.out.println("ThreadC notified.");
  18. }
  19. }
  20. });
  21. threadA.start();
  22. threadB.start();
  23. Thread.sleep(200);
  24. threadC.start();
  25. }
  26. @Override
  27. public void run() {
  28. synchronized (resourceA) {
  29. System.out.println(Thread.currentThread().getName()+" got resourceA lock.");
  30. try {
  31. System.out.println(Thread.currentThread().getName()+" waits to start.");
  32. resourceA.wait();
  33. System.out.println(Thread.currentThread().getName()+"'s waiting to end.");
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. }
  39. }

使用notifyAll()的打印结果:

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

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

  1. package threadcoreknowledge.threadobjectclasscommonmethods;
  2. /**
  3. * 描述: 3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。notify, notifyAll。 start先执行不代表线程先启动。
  4. */
  5. public class WaitNotifyAll implements Runnable {
  6. private static final Object resourceA = new Object();
  7. public static void main(String[] args) throws InterruptedException {
  8. Runnable r = new WaitNotifyAll();
  9. Thread threadA = new Thread(r);
  10. Thread threadB = new Thread(r);
  11. Thread threadC = new Thread(new Runnable() {
  12. @Override
  13. public void run() {
  14. synchronized (resourceA) {
  15. // resourceA.notifyAll();
  16. resourceA.notify();
  17. System.out.println("ThreadC notified.");
  18. }
  19. }
  20. });
  21. threadA.start();
  22. threadB.start();
  23. Thread.sleep(200);
  24. threadC.start();
  25. }
  26. @Override
  27. public void run() {
  28. synchronized (resourceA) {
  29. System.out.println(Thread.currentThread().getName()+" got resourceA lock.");
  30. try {
  31. System.out.println(Thread.currentThread().getName()+" waits to start.");
  32. resourceA.wait();
  33. System.out.println(Thread.currentThread().getName()+"'s waiting to end.");
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. }
  39. }

使用notify()打印结果

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

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

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

  1. package threadcoreknowledge.threadobjectclasscommonmethods;
  2. /**
  3. * 描述: 证明wait只释放当前的那把锁
  4. */
  5. public class WaitNotifyReleaseOwnMonitor {
  6. private static volatile Object resourceA = new Object();
  7. private static volatile Object resourceB = new Object();
  8. public static void main(String[] args) {
  9. Thread thread1 = new Thread(new Runnable() {
  10. @Override
  11. public void run() {
  12. synchronized (resourceA) {
  13. System.out.println("ThreadA got resourceA lock.");
  14. synchronized (resourceB) {
  15. System.out.println("ThreadA got resourceB lock.");
  16. try {
  17. System.out.println("ThreadA releases resourceA lock.");
  18. resourceA.wait();
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. }
  24. }
  25. });
  26. Thread thread2 = new Thread(new Runnable() {
  27. @Override
  28. public void run() {
  29. try {
  30. Thread.sleep(1000);
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. synchronized (resourceA) {
  35. System.out.println("ThreadB got resourceA lock.");
  36. System.out.println("ThreadB tries to resourceB lock.");
  37. synchronized (resourceB) {
  38. System.out.println("ThreadB got resourceB lock.");
  39. }
  40. }
  41. }
  42. });
  43. thread1.start();
  44. thread2.start();
  45. }
  46. }

打印结果:

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

  1. package threadcoreknowledge.threadobjectclasscommonmethods;
  2. /**
  3. * 展示线程sleep的时候不释放synchronized的monitor,等sleep时间到了以后,正常结束后才释放锁
  4. */
  5. public class SleepDontReleaseMonitor implements Runnable {
  6. public static void main(String[] args) {
  7. SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
  8. new Thread(sleepDontReleaseMonitor).start();
  9. new Thread(sleepDontReleaseMonitor).start();
  10. }
  11. @Override
  12. public void run() {
  13. syn();
  14. }
  15. private synchronized void syn() {
  16. System.out.println("线程" + Thread.currentThread().getName() + "获取到了monitor。");
  17. try {
  18. Thread.sleep(5000);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. System.out.println("线程" + Thread.currentThread().getName() + "退出了同步代码块");
  23. }
  24. }

执行结果:

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

(2)sleep不释放lock

  1. package threadcoreknowledge.threadobjectclasscommonmethods;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;
  4. /**
  5. * 描述: 演示sleep不释放lock(lock需要手动释放)
  6. */
  7. public class SleepDontReleaseLock implements Runnable {
  8. private static final Lock lock = new ReentrantLock();
  9. @Override
  10. public void run() {
  11. lock.lock();
  12. System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁");
  13. try {
  14. Thread.sleep(5000);
  15. System.out.println("线程" + Thread.currentThread().getName() + "已经苏醒");
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. } finally {
  19. lock.unlock();
  20. }
  21. }
  22. public static void main(String[] args) {
  23. SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
  24. new Thread(sleepDontReleaseLock).start();
  25. new Thread(sleepDontReleaseLock).start();
  26. }
  27. }

执行结果:

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

2. sleep响应中断

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

  1. package threadcoreknowledge.threadobjectclasscommonmethods;
  2. import java.util.Date;
  3. import java.util.concurrent.TimeUnit;
  4. /**
  5. * 描述: 每个1秒钟输出当前时间,被中断,观察。
  6. * Thread.sleep()
  7. * TimeUnit.SECONDS.sleep() 方便开发人员对时间的把控,不需要通过毫秒换算
  8. */
  9. public class SleepInterrupted implements Runnable{
  10. public static void main(String[] args) throws InterruptedException {
  11. Thread thread = new Thread(new SleepInterrupted());
  12. thread.start();
  13. Thread.sleep(6500);
  14. thread.interrupt();
  15. }
  16. @Override
  17. public void run() {
  18. for (int i = 0; i < 10; i++) {
  19. System.out.println(new Date());
  20. try {
  21. // TimeUnit.HOURS.sleep(3);
  22. // TimeUnit.MINUTES.sleep(25);
  23. TimeUnit.SECONDS.sleep(3);
  24. } catch (InterruptedException e) {
  25. System.out.println("我被中断了!");
  26. e.printStackTrace();
  27. }
  28. }
  29. }
  30. }

执行结果:

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

四、join()

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

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

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

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

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

  1. public class Join {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread thread = new Thread(() -> {
  4. try {
  5. System.out.println(Thread.currentThread().getName() + "开始执行");
  6. Thread.sleep(1000);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. System.out.println(Thread.currentThread().getName() + "执行完毕");
  11. });
  12. Thread thread2 = new Thread(() -> {
  13. try {
  14. System.out.println(Thread.currentThread().getName() + "开始执行");
  15. Thread.sleep(1000);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. System.out.println(Thread.currentThread().getName() + "执行完毕");
  20. });
  21. thread.start();
  22. thread2.start();
  23. System.out.println("开始等待子线程运行完毕");
  24. thread.join();
  25. thread2.join();
  26. System.out.println("所有子线程执行完毕");
  27. }
  28. }

打印结果:

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

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

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

2. join遇到中断

  1. /******
  2. * Join的中断演示
  3. */
  4. public class JoinInterrupted {
  5. public static void main(String[] args) {
  6. Thread mainThread = Thread.currentThread();
  7. Thread thread1 = new Thread(new Runnable() {
  8. @Override
  9. public void run() {
  10. try {
  11. //中断主线程
  12. mainThread.interrupt();
  13. Thread.sleep(5000);
  14. System.out.println("Thread1 sleep 结束");
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. System.out.println("子线程执行完毕");
  19. }
  20. });
  21. thread1.start();
  22. System.out.println("等待子线程运行完毕");
  23. try {
  24. thread1.join();
  25. } catch (InterruptedException e) {
  26. System.out.println(Thread.currentThread().getName()+"主线程被中断");
  27. e.printStackTrace();
  28. }
  29. System.out.println("主线程等待子线程执行完毕");
  30. }
  31. }

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

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

  1. public class JoinInterrupted {
  2. public static void main(String[] args) {
  3. Thread mainThread = Thread.currentThread();
  4. Thread thread1 = new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. try {
  8. //中断 主线程
  9. mainThread.interrupt();
  10. Thread.sleep(5000);
  11. System.out.println("Thread1 执行完毕");
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. System.out.println("子线程中断");
  15. }
  16. }
  17. });
  18. thread1.start();
  19. System.out.println("等待子线程运行完毕");
  20. try {
  21. thread1.join();
  22. } catch (InterruptedException e) {
  23. System.out.println(Thread.currentThread().getName()+"主线程被中断");
  24. e.printStackTrace();
  25. //中断 子线程
  26. thread1.interrupt();
  27. }
  28. System.out.println("子线程运行完毕");
  29. }
  30. }

打印结果:

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

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

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

代码演示:

  1. /******
  2. * 先join,再mainThread.getState();
  3. * 通过debug,看线程状态
  4. */
  5. public class JoinThreadState {
  6. public static void main(String[] args) throws InterruptedException {
  7. Thread mainThread = Thread.currentThread();
  8. Thread thread = new Thread(new Runnable() {
  9. @Override
  10. public void run() {
  11. try {
  12. Thread.sleep(3000);
  13. //查看主线程状态
  14. System.out.println(mainThread.getState());
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. });
  20. thread.start();
  21. System.out.println("子线程启动");
  22. thread.join();
  23. System.out.println("子线程运行完毕");
  24. }
  25. }

打印结果:

3. join 源码

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

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

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

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

五、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会处于空的状态,这时候消费者这边检查到没有数据消费就处于等待状态,生产者那边生产出一条数据后会唤醒消费者继续消费数据。
  1. // 数据容器:用于存储生产出的数据,并定义该容器的两个方法:生产数据的put方法和消费数据的take方法。
  2. class EventStorage {
  3. private int maxSize;
  4. private LinkedList<Date> storage;
  5. public EventStorage() {
  6. maxSize = 10;
  7. storage = new LinkedList<>();
  8. }
  9. //生产产品
  10. public synchronized void put() {
  11. //如果满了
  12. while (storage.size() == maxSize) {
  13. try {
  14. wait();
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. //如果没满
  20. storage.add(new Date());
  21. System.out.println("仓库里有了" + storage.size() + "个产品。");
  22. // 每次生产都执行一次唤醒
  23. notify();
  24. }
  25. //消费产品
  26. public synchronized void take() {
  27. //如果空了
  28. while (storage.size() == 0) {
  29. try {
  30. wait();
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. //如果没空
  36. System.out.println("拿到了" + storage.poll() + ",现在仓库还剩下" + storage.size());
  37. // 每次消费都执行一次唤醒
  38. notify();
  39. }
  40. }
  41. // 生产者类: 用于生产数据,可以看到,生产者中storage用于存储生产出的数据,run方法用于完成生产数据的任务。
  42. class Producer implements Runnable {
  43. private EventStorage storage;
  44. public Producer(EventStorage storage) {
  45. this.storage = storage;
  46. }
  47. @Override
  48. public void run() {
  49. for (int i = 0; i < 100; i++) {
  50. storage.put();
  51. }
  52. }
  53. }
  54. // 消费者类: 用于消费数据
  55. class Consumer implements Runnable {
  56. private EventStorage storage;
  57. public Consumer(EventStorage storage) {
  58. this.storage = storage;
  59. }
  60. @Override
  61. public void run() {
  62. for (int i = 0; i < 100; i++) {
  63. storage.take();
  64. }
  65. }
  66. }
  67. // 主类: 启动一个线程生产数据,另一个线程消费数据。
  68. public class ProducerConsumerModel {
  69. public static void main(String[] args) {
  70. EventStorage eventStorage = new EventStorage();
  71. Producer producer = new Producer(eventStorage);
  72. Consumer consumer = new Consumer(eventStorage);
  73. new Thread(producer).start();
  74. new Thread(consumer).start();
  75. }
  76. }

打印结果:

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

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

  1. 偶线程:0
  2. 奇线程:1
  3. 偶线程:2
  4. 奇线程:3

(1)使用synchronized关键字实现

分析:

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

代码展示:

  1. public class WaitNotifyPrintOddEvenSyn {
  2. private static int count;
  3. private static final Object lock = new Object();
  4. /**
  5. * 新建2个线程,第一个只处理偶数,第二个只处理奇数(用位运算);用synchronized来通信
  6. */
  7. public static void main(String[] args) {
  8. new Thread(() -> {
  9. while (count < 100) {
  10. synchronized (lock) {
  11. if ((count & 1) == 0) {
  12. System.out.println(Thread.currentThread().getName() + ":" + count++);
  13. }
  14. }
  15. }
  16. }, "偶线程").start();
  17. new Thread(() -> {
  18. while (count < 100) {
  19. synchronized (lock) {
  20. if ((count & 1) == 1) {
  21. System.out.println(Thread.currentThread().getName() + ":" + count++);
  22. }
  23. }
  24. }
  25. }, "奇线程").start();
  26. }
  27. }

打印结果:

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

分析:

  • 偶数线程拿到锁打印输出同时count++,然后进行休眠,因为wait()方法的特性,休眠的同时会释放monitor锁,奇数线程就可以进来了,进来后打印输出,同时notify唤醒偶数线程继续下一轮,奇数线程往下执行wait方法休眠,就这样,偶数线程唤醒奇数线程,奇数线程唤醒偶数线程,直到满足count<100条件后,线程不再休眠,直接退出程序。
  • 这个要点一个在于wait/notify的等待唤醒机制,一个在于wait()方法的特性,休眠后会释放锁。
  • 这种方式和上面那种方式不同点在于,这种方式是被动唤醒的机制,而上面那个是线程不断重试的机制(一直while重试,直到满足条件就打印,有些浪费资源),很明显这种方式优于上面那种!
  1. public class WaitNotifyPrintOddEveWait {
  2. private static int count = 0;
  3. private static final Object lock = new Object();
  4. public static void main(String[] args) {
  5. new Thread(new TurningRunner(), "偶线程").start();
  6. new Thread(new TurningRunner(), "奇线程").start();
  7. }
  8. /**
  9. * 1. 拿到锁,立刻打印
  10. * 2. 打印完,唤醒其他线程,自己就休眠
  11. */
  12. static class TurningRunner implements Runnable {
  13. @Override
  14. public void run() {
  15. while (count < 100) {
  16. synchronized (lock) {
  17. //拿到锁就打印
  18. System.out.println(Thread.currentThread().getName() + ":" + count++);
  19. lock.notify();
  20. if (count < 100) {
  21. try {
  22. //如果任务还没结束,就让出当前的锁,并休眠
  23. lock.wait();
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }
  29. }
  30. }
  31. }
  32. }

文章来源:wait,notify,notifyAll,sleep,join等线程方法的全方位演练

个人微信:CaiBaoDeCai

微信公众号名称:Java知者

微信公众号 ID: JavaZhiZhe

谢谢关注!

wait,notify,notifyAll,sleep,join等线程方法的全方位演练的更多相关文章

  1. 零基础学习java------day18------properties集合,多线程(线程和进程,多线程的实现,线程中的方法,线程的声明周期,线程安全问题,wait/notify.notifyAll,死锁,线程池),

    1.Properties集合 1.1 概述: Properties类表示了一个持久的属性集.Properties可保存在流中或从流中加载.属性列表中每个键及其对应值都是一个字符串 一个属性列表可包含另 ...

  2. wait(),notify(),notifyAll()用来操作线程为什么定义在Object类中?

    这些方法存在于同步中: 使用这些方法必须标识同步所属的锁: 锁可以是任意对象,所以任意对象调用方法一定定义在Object类中. Condition是在java 1.5中才出现的,它用来替代传统的Obj ...

  3. java 并发——理解 wait / notify / notifyAll

    一.前言 前情简介: java 并发--内置锁 java 并发--线程 java 面试是否有被问到过,sleep 和 wait 方法的区别,关于这个问题其实不用多说,大多数人都能回答出最主要的两点区别 ...

  4. 使用Object的wait,notify,notifyAll做线程调度

    我们知道java中的所有类的祖先都是Object,Object类有四个个方法wait(),wait(long timeout),notify(),notifyAll(),这四个方法可以用来做线程的调度 ...

  5. wait() 与 notify/notifyAll()

    wait() 与 notify/notifyAll() 是Object类的方法 1. wait() 与notify/notifyAll方法必须在同步代码块中使用 在执行以上方法时,要先获得锁.那么怎么 ...

  6. -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

     本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁  sleep()和wait()方法的区别 为什么wait( ...

  7. 线程sleep,wait,notify,join,yield方法解析

    线程的五种状态 线程从创建到销毁一般分为五种状态,如下图: 1) 新建 当用new关键字创建一个线程时,就是新建状态. 2) 就绪 调用了 start 方法之后,线程就进入了就绪阶段.此时,线程不会立 ...

  8. 并发编程——线程中sleep(),yield(),join(),wait(),notify(),notifyAll()区别

    前言 今天简单的讲一讲线程中sleep(),join(),yield(),wait(),notify(),notifyAll()这些方法的使用以及区别. 不过在讲这些方法之前,需要简单的介绍一下锁池和 ...

  9. 【转】wait,notify,notifyAll,join,yield,sleep的区别和联系

    1.  Thread.sleep(long) 和Thread.yield()都是Thread类的静态方法,在调用的时候都是Thread.sleep(long)/Thread.yield()的方式进行调 ...

  10. java ---线程wait/notify/sleep/yield/join

    一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线 ...

随机推荐

  1. C++温故补缺(十九):atomic类

    atomic 参考:c++11 多线程(3)atomic 总结 - 简书.c++11 atomic Npgw的博客.C++11 并发指南系列 - Haippy - 博客园. atomic_flag a ...

  2. while与do-while的区别是什么,怎么用?

    前言 在上一篇文章中,壹哥给大家讲解了循环的概念,并重点给大家讲解了for循环的使用.但在Java中,除了for循环之外,还有while.do-while.foreach等循环形式.今天壹哥就再用一篇 ...

  3. Linux基础知识归纳

    1.Linux:Linux is not Unix.主要用于企业的服务器端.Windows不开源(系统价格大概2000左右,安装软件也特别贵,例如Offers就6000左右等).基于内核的操作系统(r ...

  4. SHA-256 简介及 C# 和 js 实现【加密知多少系列】

    〇.简介 SHA-256 是 SHA-2 下细分出的一种算法.截止目前(2023-03)未出现"碰撞"案例,被视为是绝对安全的加密算法之一. SHA-2(安全散列算法 2:Secu ...

  5. SpringBoot 项目使用 Sa-Token 完成登录认证

    一.设计思路 对于一些登录之后才能访问的接口(例如:查询我的账号资料),我们通常的做法是增加一层接口校验: 如果校验通过,则:正常返回数据. 如果校验未通过,则:抛出异常,告知其需要先进行登录. 那么 ...

  6. Android APP开机启动,安卓APP开发自启动,安卓启动后APP自动启动 Android让程序开机自动运行APP 全开源下载

    让APP在安卓系统启动自动运行可以带来以下几个好处:用户方便:当用户打开设备时,自动启动所需的APP可以让用户更方便地使用设备,不必手动打开APP.提高用户黏性:自动启动APP可以让用户更快地开始使用 ...

  7. pychearm日常用法

    一 常用快捷键 编辑类:Ctrl + D             复制选定的区域或行Ctrl + Y           删除选定的行Ctrl + Alt + L     代码格式化Ctrl + Al ...

  8. 通过python修改本地ip

    写在前面, 1 对于个人公司需要固定ip,而回家需要用到家里的ip, 2对于公司it人员,每台电脑都需要设置ip,,尤其批量的时候,这个作为it的自己知道 3运维人员,可以通过ip测试哪些ip可以用, ...

  9. 全网最详细中英文ChatGPT-GPT-4示例文档-食谱智能生成从0到1快速入门——官网推荐的48种最佳应用场景(附python/node.js/curl命令源代码,小白也能学)

    目录 Introduce 简介 setting 设置 Prompt 提示 Sample response 回复样本 API request 接口请求 python接口请求示例 node.js接口请求示 ...

  10. LeeCode 动态规划(三)

    完全背包问题 题目描述 有 n 件物品和容量为 w 的背包,给你两个数组 weights 和 values,分别表示第 i 件物品的重量和价值,每件物品可以放入多次,求解将哪些物品装入背包可使得物品价 ...