前言

多线程时,最关注的就是线程同步,线程间的同步一般用锁来实现,常见的锁就是synchronized和lock。用了synchronized,就不得不提到wait/notify/notifyAll。本文介绍这三者是什么东西。

举例说明

首先明确一点,所有的锁都是加在对象上面的。也就是说,只要是加了同步synchronized的代码,每个线程在运行到这的时候,都要去查一下这个对象上的锁有没有被人占用,如果被人占用了,就要等待。等待也分两种,一种是一直等着,一种是先做别的,别人提醒之后再找。

举个生活中的例子做对比。假设有一个屋子,屋子有一个门,门上面有一个锁。屋子同一个时间只能有一个人,这个人在里面干什么没人管。现在有多个人想进到这个屋子里面,每个进去的人都可以锁门。synchronized(object)里面的object就是门上这把锁,synchronized代码的意思是在执行代码之前,看看这把锁是不是锁着的,如果不是锁着的,就进去工作,同时把门锁上。执行完{}代码之后,就相当于从屋子里面出来,同时打开了锁。wait的意思就某个人甲进入了屋子,工作了一会,想临时先出去。所以他把锁打开,出去了,并把屋子让给了别人。那他什么时候回来呢?必须有别人告诉他,也就是notify。notify和notifyAll的区别在于notify随机告诉某一个人(注意这个人是wait的),我不用锁了,你用吧;notifyAll是告诉所有等待这个锁的人。

我们下面用这个例子加代码说明这些用法。

情况1:不锁门,不加锁

如果不锁门,就会有多个人进入到这个屋子,这个屋子就是被撑爆。也就是说,如果代码里面不加synchronize,会有多个进程访问同一个资源,造成不可知的错误。这种情况就不举代码的例子了。

情况2:锁门,不加wait和notify

这种情况下,代码如WaitNotifyDemo3所示:

  1. package com.wardensky.multithread.waitnotify;
  2. /**
  3. * 这个例子就是加锁了,但没有加wait和notify
  4. *
  5. * @author zch
  6. *
  7. */
  8. public class WaitNotifyDemo3 {
  9. /**
  10. * 这个对象就是所有线程都用的锁。
  11. */
  12. public static Object obj = new Object();
  13. public static void main(String[] args) throws InterruptedException {
  14. Thread0_1 t1 = new Thread0_1("Thread WaterMelon");
  15. Thread0_2 t2 = new Thread0_2("Thread Apple");
  16. t2.start();
  17. Thread.sleep(1);
  18. t1.start();
  19. System.out.println("完成");
  20. }
  21. }
  22. /**
  23. * 这个线程用来演示阻塞,等这个线程工作完,不会告诉别人。<br/>
  24. *
  25. * @author zch <br/>
  26. *
  27. */
  28. class Thread3_1 extends Thread {
  29. String name;
  30. public Thread3_1(String name) {
  31. this.name = name;
  32. }
  33. @Override
  34. public void run() {
  35. try {
  36. synchronized (WaitNotifyDemo3.obj) {
  37. for (int i = 1; i < 5; i++) {
  38. Thread.sleep(1000);
  39. System.out.println("I'm " + this.name + ". I'm doing something " + i);
  40. }
  41. }
  42. } catch (InterruptedException e1) {
  43. e1.printStackTrace();
  44. }
  45. }
  46. }
  47. /**
  48. * 这个类用来表示被阻塞的线程,但这个线程并没有wait,所以他一直轮询这个锁,比较占资源。<br/>
  49. *
  50. * @author zch
  51. *
  52. */
  53. class Thread3_2 extends Thread {
  54. String name;
  55. public Thread3_2(String name) {
  56. this.name = name;
  57. }
  58. @Override
  59. public void run() {
  60. System.out.println("I'm " + this.name + " . I'm waiting");
  61. synchronized (WaitNotifyDemo3.obj) {
  62. System.out.println(this.name + " Finish wait");
  63. }
  64. }
  65. }

输出如下:

  1. I'm Thread Apple . I'm waiting
  2. 完成
  3. I'm Thread WaterMelon. I'm doing something 1
  4. I'm Thread WaterMelon. I'm doing something 2
  5. I'm Thread WaterMelon. I'm doing something 3
  6. I'm Thread WaterMelon. I'm doing something 4
  7. I'm Thread WaterMelon. The object notify
  8. Thread Apple Finish wait

在上面这个例子里面,Apple线程一直在轮询这个锁的状态,等到线程WaterMelon用完之后,马上抢占过来。这种情况下,比较耗资源。

情况3: 加wait和notify

我们在情况2的基础上,让Apple线程增加一个wait,让线程WaterMelon增加一个notify,代码如下:

  1. package com.wardensky.multithread.waitnotify;
  2. public class WaitNotifyDemo0 {
  3. /**
  4. * 这个对象就是所有线程都用的锁。
  5. */
  6. public static Object obj = new Object();
  7. public static void main(String[] args) throws InterruptedException {
  8. Thread0_1 t1 = new Thread0_1("Thread WaterMelon");
  9. Thread0_2 t2 = new Thread0_2("Thread Apple");
  10. t2.start();
  11. ///这个sleep很重要。
  12. Thread.sleep(1);
  13. t1.start();
  14. System.out.println("完成");
  15. }
  16. }
  17. /**
  18. * 这个线程用来演示阻塞,等这个线程工作完,会通过notify方法告诉别人。<br/>
  19. *
  20. * @author zch <br/>
  21. *
  22. */
  23. class Thread0_1 extends Thread {
  24. String name;
  25. public Thread0_1(String name) {
  26. this.name = name;
  27. }
  28. @Override
  29. public void run() {
  30. try {
  31. synchronized (WaitNotifyDemo0.obj) {
  32. for (int i = 1; i < 5; i++) {
  33. Thread.sleep(1000);
  34. System.out.println("I'm " + this.name + ". I'm doing something " + i);
  35. }
  36. WaitNotifyDemo0.obj.notify();
  37. System.out.println("I'm " + this.name + ". The object notify");
  38. }
  39. } catch (InterruptedException e1) {
  40. e1.printStackTrace();
  41. }
  42. }
  43. }
  44. /**
  45. * 这个类用来表示被阻塞的线程,进入工作状态后,他可以先把锁还给别人,等别人通知他,他才继续工作。<br/>
  46. *
  47. * @author zch
  48. *
  49. */
  50. class Thread0_2 extends Thread {
  51. String name;
  52. public Thread0_2(String name) {
  53. this.name = name;
  54. }
  55. @Override
  56. public void run() {
  57. try {
  58. System.out.println("I'm " + this.name + " . I'm waiting");
  59. synchronized (WaitNotifyDemo0.obj) {
  60. WaitNotifyDemo0.obj.wait();
  61. System.out.println(this.name + " Finish wait");
  62. }
  63. } catch (InterruptedException e1) {
  64. e1.printStackTrace();
  65. }
  66. }
  67. }

输出:

  1. I'm Thread Apple . I'm waiting
  2. 完成
  3. I'm Thread WaterMelon. I'm doing something 1
  4. I'm Thread WaterMelon. I'm doing something 2
  5. I'm Thread WaterMelon. I'm doing something 3
  6. I'm Thread WaterMelon. I'm doing something 4
  7. I'm Thread WaterMelon. The object notify
  8. Thread Apple Finish wait

在这个例子里面,Thread0_2这个线程在synchronized (WaitNotifyDemo0.obj)之后调用了WaitNotifyDemo0.obj.wait();,意思是 我进入了屋子之后,先离开了,让别人处理,但别人处理之后要记得notify我,不然我就不知道了。

需要注意的是,在这个例子里面,两个线程的启动之间有一个sleep,这个sleep很重要,下面会讲到。

情况4:有多个人wait

如果有多个人wait,则唤醒线程尽量用notifyAll。notify是告诉某一个线程,但具体是哪个线程是随机的。notifyAll就是大家再来竞争。

如果用了没有用notifyAll,也可以wait执行之后调用notify,也就是一个执行完再唤醒另外一个。代码示例如下:

  1. package com.wardensky.multithread.waitnotify;
  2. public class WaitNotifyDemo1 {
  3. public static Object obj = new Object();
  4. public static void main(String[] args) {
  5. Thread1_1 t1 = new Thread1_1();
  6. Thread1_2 t2 = new Thread1_2("Thread Apple");
  7. Thread1_2 t3 = new Thread1_2("Thread Orange");
  8. t2.start();
  9. t3.start();
  10. try {
  11. // 必须sleep一下
  12. Thread.sleep(1);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. t1.start();
  17. System.out.println("完成");
  18. }
  19. }
  20. class Thread1_1 extends Thread {
  21. @Override
  22. public void run() {
  23. try {
  24. synchronized (WaitNotifyDemo1.obj) {
  25. for (int i = 1; i < 5; i++) {
  26. Thread.sleep(1000);
  27. System.out.println("I'm thread111. I'm doing something " + i);
  28. }
  29. // WaitNotifyDemo.obj.notify();
  30. WaitNotifyDemo1.obj.notifyAll();
  31. System.out.println("I'm thread111. The object notify");
  32. }
  33. } catch (InterruptedException e1) {
  34. e1.printStackTrace();
  35. }
  36. }
  37. }
  38. class Thread1_2 extends Thread {
  39. String name;
  40. public Thread1_2(String name) {
  41. this.name = name;
  42. }
  43. @Override
  44. public void run() {
  45. try {
  46. System.out.println("I'm " + this.name + " . I'm waiting");
  47. synchronized (WaitNotifyDemo1.obj) {
  48. WaitNotifyDemo1.obj.wait();
  49. System.out.println(this.name + " Finish wait");
  50. // 如果不加这句话,就叫不醒别人了。
  51. // WaitNotifyDemo.obj.notify();
  52. }
  53. } catch (InterruptedException e1) {
  54. e1.printStackTrace();
  55. }
  56. }
  57. }

输出:

  1. I'm Thread Apple . I'm waiting
  2. I'm Thread Orange . I'm waiting
  3. 完成
  4. I'm thread111. I'm doing something 1
  5. I'm thread111. I'm doing something 2
  6. I'm thread111. I'm doing something 3
  7. I'm thread111. I'm doing something 4
  8. I'm thread111. The object notify
  9. Thread Orange Finish wait
  10. Thread Apple Finish wait

情况5:情况2里面的不加sleep

如果情况2里面的例子不加sleep,会出现程序无法执行完的情况。原因是两个线程几乎同时运行,但线程2在同步代码之前有一个输出System.out.println("I'm " + this.name + " . I'm waiting");。这个输出会导致他比线程1晚进入同步代码。线程1notify的时候,线程2还没有等待。等到线程2真正等待的时候,已经没有人来唤醒他了。

再看看源码

前面提到过,wait/notify/notifyAll都是在对象上的。实际上,Object对象上都有这些方法。代码如下:

  1. public final native void wait(long timeout) throws InterruptedException;
  2. public final void wait(long timeout, int nanos) throws InterruptedException {
  3. if (timeout < 0) {
  4. throw new IllegalArgumentException("timeout value is negative");
  5. }
  6. if (nanos < 0 || nanos > 999999) {
  7. throw new IllegalArgumentException(
  8. "nanosecond timeout value out of range");
  9. }
  10. if (nanos > 0) {
  11. timeout++;
  12. }
  13. wait(timeout);
  14. }
  15. public final void wait() throws InterruptedException {
  16. wait(0);
  17. }
  18. public final native void notify();
  19. public final native void notifyAll();

从源码中可以看出notify();notifyAll();都是native的方法。

wait还有几个重载,可以指定等多久。比如下面的代码:

  1. @Override
  2. public void run() {
  3. System.out.println("I'm " + this.name + " . I'm waiting");
  4. try {
  5. synchronized (WaitNotifyDemo5.obj) {
  6. WaitNotifyDemo5.obj.wait(1000 * 1);
  7. System.out.println(this.name + " Finish wait");
  8. }
  9. } catch (Exception e1) {
  10. e1.printStackTrace();
  11. }
  12. }

这个代码的意思是我等1秒钟,1秒之后就进入抢占式了。当前,1秒之前也可以用notify或者notifyAll叫醒我。

跟Thread.sleep的关系

线程还有一个sleep静态方法,有人会混淆sleep和wait的关系。

还用上面的例子,sleep的意思是这个人还在屋子里面睡觉,门还锁着呢,谁也进不来。wait的意思是我把门打开,出去睡觉,等着别人把我叫醒我再回来。

也就是说,wait会释放锁,sleep不会。wait是锁这个对象的方法,sleep是线程的静态方法。

synchronized

synchronized(obj)一个代码段,意思是下面{} 里面的代码执行的时候,要去检查obj上面的锁有没有被别人占用。

同时还有这样一种写法,这是StringBuffer里面的源码:

  1. @Override
  2. public synchronized StringBuffer append(Object obj) {
  3. toStringCache = null;
  4. super.append(String.valueOf(obj));
  5. return this;
  6. }

这段代码相当于

  1. @Override
  2. public StringBuffer append(Object obj) {
  3. synchronized(this) {
  4. toStringCache = null;
  5. super.append(String.valueOf(obj));
  6. return this;
  7. }
  8. }

也就是说加锁的对象是this。

如果是静态方法,那么加锁的对象是obj.class,该类的类对象。

总结

  • synchronized是要检查锁的代码,如果有锁不往下执行。
  • wait只能在同步(synchronize)环境中被调用
  • 进入wait状态的线程能够被notify和notifyAll线程唤醒
  • wait通常有条件地执行,线程会一直处于wait状态,直到某个条件变为真
  • wait方法在进入wait状态的时候会释放对象的锁
  • wait方法是针对一个被同步代码块加锁的对象
  • sleep方法和多线程没有直接关系,跟锁也没有关系
  • wait释放锁,notify和notifyAll不释放锁

线程中wait/notify/notifyAll的用法的更多相关文章

  1. Java多线程--wait(),notify(),notifyAll()的用法

    忙等待没有对运行等待线程的 CPU 进行有效的利用(而且忙等待消耗cpu过于恐怖,请慎用),除非平均等待时间非常短.否则,让等待线程进入睡眠或者非运行状态更为明智,直到它接收到它等待的信号. Java ...

  2. Java 多线程 线程的五种状态,线程 Sleep, Wait, notify, notifyAll

    一.先来看看Thread类里面都有哪几种状态,在Thread.class中可以找到这个枚举,它定义了线程的相关状态: public enum State { NEW, RUNNABLE, BLOCKE ...

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

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

  4. 转:【Java并发编程】之十:使用wait/notify/notifyAll实现线程间通信的几点重要说明

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17225469    在Java中,可以通过配合调用Object对象的wait()方法和no ...

  5. 【Java并发编程】:使用wait/notify/notifyAll实现线程间通信

    在java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信.在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调 ...

  6. 【Java并发编程】之十:使用wait/notify/notifyAll实现线程间通信的几点重要说明

    在Java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信.在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调 ...

  7. 使用wait/notify/notifyAll实现线程间通信的几点重要说明

    在Java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信.在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调 ...

  8. 进程间协作---wait,notify,notifyAll

    转自牛客网的一篇评论,解释的十分详细 在 Java 中,可以通过配合调用 Object 对象的 wait() 方法和 notify()方法或 notifyAll() 方法来实现线程间的通信.在线程中调 ...

  9. 多线程-wait/notify/notifyAll

    引言 在Java中,可以通过配合调用Object对象的wait,notify和notifyAll来实现线程间的通信. 在线程中调用wait方法,将阻塞带带其他线程的通知(其他线程调用notify或no ...

随机推荐

  1. 猫咪记单词——NABCD模型分析

    N ——Need 需求:学习英语是一件非常重要的事.面对各种各样的考试,学习英语,最重要的就是词汇量,背单词是提高词汇量的最直接的方法,但是单纯的背单词太单调.寻找一些合适的,更易于接受的背单词学习英 ...

  2. JVM面试问题

    JVM主要包括:程序计数器(Program Counter),Java堆(Heap),Java虚拟机栈(Stack),本地方法栈(Native Stack),方法区(Method Area) 1.程序 ...

  3. Scrum Meeting Beta - 8

    Scrum Meeting Beta - 8 NewTeam 2017/12/7 地点:新主楼F座二楼 任务反馈 团队成员 完成任务 计划任务 安万贺 完成了博文详情的存储Issue #150Pull ...

  4. Rsyslog日志服务搭建

    rsyslog是比syslog功能更强大的日志记录系统,可以将日志输出到文件,数据库和其它程序.Centos 6.x默认的rsyslog版本是5.x. 网上关于rsyslog的安装配置文档倒是不少,但 ...

  5. webpack命令局部运行的几种方法

    webpack命令局部运行的几种方法   1. 第一种,先全局安装webpack 命令:npm install -g webpack 然后再在项目内安装 命令:npm install webpack ...

  6. 如何实现MySQL表数据随机读取?从mysql表中读取随机数据

    文章转自 http://blog.efbase.org/2006/10/16/244/如何实现MySQL表数据随机读取?从mysql表中读取随机数据?以前在群里讨论过这个问题,比较的有意思.mysql ...

  7. Android 去掉标题全屏显示

    自己测试时出现无法实现去掉标题和全屏功能.最后发现只要public class SocketActivity extends Activity {} 而不能用ActionBarActivity. 先介 ...

  8. js遍历数组和遍历对象

    可以用for in来遍历对象,具体内容如下: <script type="text/javascript">             var objs = {      ...

  9. 队列java实现

    队列是一种线性数据结构,是一种运算受限的线性表,只允许在队尾插入,在队头删除.运算规则是先进先出.恰好和栈相反.栈是先进后出.因为栈只在栈顶做删除和插入. 队列按照存储结构可以分为顺序队列和链式队列. ...

  10. TypeError: to_categorical() got an unexpected keyword argument 'nb_classes'

    在学习莫烦教程中keras教程时,报错:TypeError: to_categorical() got an unexpected keyword argument 'nb_classes',代码如下 ...