前言
线程并发系列文章:
上篇分析了重量级锁在线程互斥场景下加锁、释放锁的过程,本篇将分析重量级锁在线程同步下的等待、通知机制。
通过本篇文章,你将了解到:
1、为什么 wait/notify/notifyAll 需要上锁
2、wait/notify/notifyAll 源码入口
3、Object.wait(xx) 流程解析
4、Object.notify()/Object.notifyAll() 流程解析
5、wait/notify/notifyAll 流程图
6、线程互斥同步下 锁的流程图
7、wait/notify/notifyAll 疑难点解析
1、为什么 wait/notify/notifyAll 需要上锁
一个Demo
线程同步需要在某种条件下调用wait/notify进行同步,先来看简单的例子:
public class TestThread {
static Object object = new Object();
static int count = 1;
public static void main(String args[]) {
//线程 A 消费者
new Thread(new Runnable() {
@Override
public void run() {
try {
count--;
if (count == 0) {
//count == 0 才会等待
object.wait();
}
} catch (Exception e) {
}
}
}).start();
//线程 B 生产者
new Thread(new Runnable() {
@Override
public void run() {
try {
//生产好了就通知线程A
count++;
object.notify();
} catch (Exception e) {
}
}
}).start();
}
}
上述功能很简单:线程B生产东西(增加count值),线程A消费东西(减少count值),线程A发现没东西可用了就调用wait挂起等待相应的条件满足后再次运行。
此处的条件即是:count的值。
正常情况下是:线程A等待count值,线程B通知线程A count值已经准备好了,这就是线程之间的同步。
wait 之前为什么需要获取锁
现在从多线程并发的角度来看这Demo,可能的运行顺序如下:
1、count初始值为1。
2、线程A先执行到count==0,准备调用object.wait()。
3、此时线程B已经修改好了count值,并且调用了Object.notify()。
4、线程A此时调用Object.wait()后,因为错过了Object.notify(),所以就永远阻塞于此处。
导致上面问题的原因是:count是线程间共享的,对它的修改存在并发问题,因此需要加锁来实现互斥访问count。
notify 之前为什么需要获取锁
你也许会说:既然锁是为了保护count,那么只保护对应的共享变量即可,notify可以不上锁啊。如下代码:
//线程A
synchronized (object) {
count--;
if (count == 0) {
//count == 0 才会等待
object.wait();
}
}
//线程B
synchronized (object) {
//生产好了就通知线程A
count++;
}
object.notify();
notify 的目的是将等待队列里的线程插入到同步队列里,假设是notify没有在同步代码块里,那么线程B修改count值后释放锁,因为还没有notify,因此A没有移动到同步队列里,最终无法唤醒线程A,A就会一直阻塞等待。
notifyAll也是一样的道理。
notify具体原理接下来会详细分析。
JVM如何避免不正常地调用
wait/notify/notifyAll 需要在同步块里调用,而用户不一定这么操作,因此JVM会在调用wait/notify/notifyAll 时检测当前线程是否已经获取了锁,没有锁则会抛出异常。
#ObjectMonitor.cpp
void ObjectMonitor::notify(TRAPS) {
CHECK_OWNER();
...
}
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
...
CHECK_OWNER();
...
}
void ObjectMonitor::notifyAll(TRAPS) {
CHECK_OWNER();
...
}
#define CHECK_OWNER() \
do { \
if (THREAD != _owner) {
//当前线程不是重量级锁的获得者 \
if (THREAD->is_lock_owned((address) _owner)) { \
//当前线程是之前轻量级锁的获得者
_owner = THREAD ; /* Convert from basiclock addr to Thread addr */ \
_recursions = 0; \
OwnerIsThread = 1 ; \
} else {
//没有获取抛出异常 \
TEVENT (Throw IMSX) ; \
THROW(vmSymbols::java_lang_IllegalMonitorStateException()); \
} \
} \
} while (false)
可以看出,调用wait/notify/notifyAll的时候会调用宏CHECK_OWNER()去检测当前线程是否获取了锁,没有则抛出IllegalMonitorStateException 异常。
小结:
wait/notify/notifyAll 需要包在synchronized 同步块里的原因是保护同步的条件在并发场景下能够被正确访问。
2、wait/notify/notifyAll 源码入口
wait/notify/notifyAll 方法是声明在Java顶级类Object.java里的,通过寻找发现是native层实现的。
#Object.c
static JNINativeMethod methods[] = {
{"hashCode", "()I", (void *)&JVM_IHashCode},
{"wait", "(J)V", (void *)&JVM_MonitorWait},
{"notify", "()V", (void *)&JVM_MonitorNotify},
{"notifyAll", "()V", (void *)&JVM_MonitorNotifyAll},
{"clone", "()Ljava/lang/Object;", (void *)&JVM_Clone},
};
可以看到是动态注册的JNI函数。
以