调用wait, notify, notifyAll的代码必须位于synchronized内,否则系统会抛出IllegalMonitorStateException
的异常。
首先来了解一下关键字 synchronized
。
synchronized
为了解决共享资源并发访问的为题,Java内置了synchronized
关键字来解决线程同步问题。先看下面这个例子,两个不同的线程同时对变量sharedResource
进行操作,就是所谓的Race Condition
。
public class RaceCondition {
int sharedResource = 0;
public void startTwoThreads() {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
sharedResource++;
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
sharedResource--;
}
});
t2.start();
}
}
这段代码是有问题的。t1
和t2
的run
方法中,变量sharedResource
的状态可能是不稳定的。这是因为sharedResource++
与sharedResource--
并不是[原子(Atomic)操作](http://www.liangfeizc.com/blog/article/21/),而且sharedResource
不是volatile
变量,它的值可能不会被及时更新,所有还存在visiblitiy
的问题。为了Thread Safety
,解决共享变量访问冲突的问题,java提供了synchronized关键字。加上了synchronized关键字后的代码如下所示:
public class RaceCondition {
int sharedResource = 0;
public synchronized void increment() {
sharedResource++;
}
public synchronized void decrement() {
sharedResource--;
}
public void startTwoThreads() {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
increment();
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
decrement();
}
});
t2.start();
}
}
这样当线程t1
进入increment
方法后就获得了整个Object的锁,t2
就无法进入decrement
方法。那synchronized
与wait
, notify
又有什么关系呢?
wait & notify
wait
与notify
是成对使用的,可以通过生产者消费者问题
来了解它们的用法。
import java.util.LinkedList;
public class ConsumerProducer {
private LinkedList list = new LinkedList();
private final int LIMIT = 10;
private Object lock = new Object();
public void produce() throws InterruptedException {
int value = 0;
while (true) {
synchronized (lock) {
while (list.size() == LIMIT) {
lock.wait();
}
list.add(value++);
lock.notify();
}
}
}
public void consume() throws InterruptedException {
while (true) {
synchronized (lock) {
while (list.size() == 0) {
lock.wait();
}
int value = list.removeFirst();
lock.notify();
}
}
}
public static void main(String[] args) {
final ConsumerProducer cp = new ConsumerProducer();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
cp.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
cp.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
}
}
通常情况下,当资源不满足条件,线程无法继续执行下去的时候,我们会调用wait
进入wait queue
,同时当前线程会释放lock,并转入sleep状态(这也是object.wait()
与Thread.sleep()
的区别之一)。当其他线程执行notify()
来唤醒进入wait状态的线程时,我们会再次查看是否满足了继续执行的条件,否则再次进入wait。
produce方法中的
while (list.size() == LIMIT) {
lock.wait();
}
会判断资源是否满足条件,而consume方法中的
int value = list.removeFirst();
lock.notify();
会发出一个资源变化的通知。
这样一来,调用produce的线程t1与调用consume的线程t2会有一个共享资源 -- list
。如果没有synchronized
关键字,会发生下面这种死锁情况。
t1执行完while (list.size() == LIMIT)
后,CPU切换到t2,继续执行list.removeFirst();lock.notify();
,而此时t1并没有进入wait
状态,所以收不到notify
发来的消息,CPU又切换回t1,执行lock.wait();
,如果此时t2线程被终止,那么t1就会一直wait下去。
所以解决wait,notify,notify
之间Race Condition
的问题,必须使用synchronized
关键字。
参考资料
- Efficient Android Threading
- Thinking in Java
- Why must wait() always be in synchronized block