前几篇复习了下线程的建立方式、线程的状态、Thread 的源码这几篇文章,这篇讲讲 Object 几个跟线程获取释放锁相关的方法:wait、notify、notifyAll。java
因为 wait() 是 Object 类的 native 方法,在 idea 中,它长这样:安全
public final native void wait(long timeout) throws InterruptedException;
看不了源码,那只能看源码的注释,注释太长,我摘取一些关键的点出来:app
一、 Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed. The current thread must own this object's monitor. 二、 In other words,waits should always occur in loops.like this one: synchronized(obj) { while (condition does not hold) obj.wait(timeout); // Perform action appropriate to condition } 三、 @throws IllegalArgumentException if the value of timeout isnegative. @throws IllegalMonitorStateException if the current thread is notthe owner of the object 's monitor. @throws InterruptedException if any thread interrupted the current thread before or while the current thread was waiting for a notification. The interrupted status of the current thread is cleared when this exception is thrown.
注释中提到几点:ide
wait 会让当前线程进入等待状态,除非其余线程调用了 notify 或者 notifyAll 方法唤醒它,又或者等待时间到。另外,当前线程必须持有对象监控器(也就是使用 synchronized 加锁)oop
必须把 wait 方法写在 synchronized 保护的 while 代码块中,并始终判断执行条件是否知足,若是知足就往下继续执行,若是不知足就执行 wait 方法,而在执行 wait 方法以前,必须先持有对象的 monitor 锁,也就是一般所说的 synchronized 加锁。this
超时时间非法,抛 IllegalArgumentException 异常;不持有对象的 monitor 锁,抛 IllegalMonitorStateException 异常;在等待期间被其余线程中断,抛出 InterruptedException 异常。idea
逆向思考下,没有 synchronized 保护的状况下,咱们使用会出现啥问题?先试着来模拟一个简单的生产者消费者例子:线程
public class BlockingQueue { Queue<String> buffer = new LinkedList<String>(); // 生产者,负责往队列放数据 public void give(String data) { buffer.add(data); notify(); } // 消费者,主要是取数据 public String take() throws InterruptedException { while (buffer.isEmpty()) { wait(); } return buffer.remove(); } }
首先 give () 往队列里放数据,放完之后执行 notify 方法来唤醒以前等待的线程;take () 检查整个 buffer 是否为空,若是为空就进入等待,若是不为空就取出一个数据。但在这里咱们并无用 synchronized 修饰。假设咱们如今只有一个生产者和一个消费者,那就有可能出现如下状况:code
此时,生产者无数据。消费者线程调用 take(),while 条件为 true。正常来讲,这时应该去调用 wait() 等待,但此时消费者在调用 wait 以前,被被调度器暂停了,还没来得及调用 wait。orm
到生产者调用 give 方法,放入数据并视图唤醒消费者线程。可这个时候唤醒不起做用呀。消费者并无在等待。
最后,消费者回去调用 wait 方法,就进入了无限等待中。
看明白了吗?第一步时,消费者判断了 while 条件,但真正执行 wait 方法时,生产者已放入数据,以前的 buffer.isEmpty 的结果已通过期了,由于这里的 “判断 - 执行” 不是一个原子操做,它在中间被打断了,是线程不安全的。
正确的写法应该是这样子的:如下写法就确保永远 notify 方法不会在 buffer.isEmpty 和 wait 方法之间被调用,也就不会有线程安全问题。
public class BlockingQueue { Queue<String> buffer = new LinkedList<String>(); // 生产者,负责往队列放数据 public void give(String data) { synchronized(this) { buffer.add(data); notify(); } } // 消费者,主要是取数据 public String take() throws InterruptedException { synchronized(this) { while (buffer.isEmpty()) { wait(); } return buffer.remove(); } } }
notify & notifyAll 都是 Object 的 native 方法,在 IDEA 中看不到它的源码,一样是只能看注释。
public final native void notify(); public final native void notifyAll();
注释中主要提到如下几点:
两点缘由:
Java 的每一个对象都有一把称之为 monitor 监视器的锁,每一个对象均可以上锁,因此在对象头中有一个用来保存锁信息的位置。这个锁是对象级别的,而不是线程级别的,每一个对象都有锁,经过线程得到。若是线程须要等待某些锁那么调用对象中的 wait 方法就有意义了,它等待的就是这个对象的锁。若是 wait 方法定义在 Thread 类中,线程正在等待的是哪一个锁就不明显了。简单来讲,因为 wait & notify & notifyAll 是锁级别的操做,因此把他们定义在 Object 类中由于锁属于对象。
再者,若是把它们定义在 Thread 中,会带来不少问题。一个线程能够有多把锁,你调用 wait 或者 notify,我怎么知道你要等待的是哪把锁?唤醒的哪一个线程呢?
上次的文章咱们已经看过了 sleep 的源码了,它们的相同点主要有:
不一样点: