LZ在网上看了很多关于这个问题的解释,都不够深入,那么今天就让我带大家深入了解这个问题。关于synchronized的详细介绍请移步大神所写的博客:深入理解Java并发之synchronized实现原理,这篇文档稍微有点长,我会用自己的话总结一下关于wait()、notify()和notifyAll()的问题。
说到Object的这三个方法,就需要先说一下synchronized关键字,我们知道每一个实例对象或是类对象都可以作为一把锁来使用,例如:
public class SynchronizedTest{
private static int i = 0;
public static synchronized void increase(){ //加锁的对象是SynchronizedTest的类对象
i++;
}
public synchronized void increase4Obj(){ //加锁的对象当时调用该方法的实例对象
i++;
}
public void increase2Obj(){
synchronized (Object.class){ //加锁的对象是Object的类对象
i++;
}
}
}
上面方式是synchronized实现锁的三种方式,这一点不懂的还请看一下我上面提到的大神写的博客,之所以每个对象都可以当成一把锁把使用,是因为每一个对象都有唯一的一个monitor对象与之关联,JVM中对象的布局如下(直接复制大神博客里面的图片)
而对于顶部,则是Java头对象,它实现synchronized的锁对象的基础,这点我们重点分析它,一般而言,synchronized使用的锁对象是存储在Java对象头里的,jvm中采用2个字来存储对象头(如果对象是数组则会分配3个字,多出来的1个字记录的是数组长度),其主要结构是由Mark Word 和 Class Metadata Address 组成,其结构说明如下表:
虚拟机位数 | 头对象结构 | 说明 |
32/64bit | Mark Word | 存储对象的hashCode、锁信息或分代年龄或GC标志等信息 |
32/64bit | Class Metadata Address | 类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的实例。 |
其中在Mark Word中的信息会根据锁的状态进行动态的变换,当锁的状态为重量级锁的时候,Mark Word中会有一个引用指向monitor对象(每一个对象都会对应一个monitor对象),monitor对象的结构如下:
ObjectMonitor() {
_header = NULL;
_count = 0; //记录重入的次数个数
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
其中比较关键的变量有
_count:记录当前持有monitor对象的线程重入的次数
_owner:记录当前持有monitor对象的线程
_WaitSet:等待集合,当前持有monitor对象的线程,调用wait()方法会释放monitor对象锁,然后进入该集合等待被唤醒
_EntryList:等待获取锁的集合,在进入synchronized方法或synchronized修饰的代码块时,竞争获取monitor对象锁失败的线程会进入该集合等待机会竞争monitor对象锁。
其中的关系如下图所示:
知道了synchronized的原理后,就可以回答上面两个问题了:
一:wait()、notify()和notifyAll()方法为什么要在synchronized代码块中?
在Object的wait()方法上面有这样一行注释:The current thread must own this object's monitor,意思是调用实例对象的wait()方法时,该线程必须拥有当前对象的monitor对象锁,而要拥有monitor对象锁就需要在synchronized修饰的方法或代码块中竞争并生成占用monitor对象锁。而不使用synchronized修饰的方法或代码块就不会占有monitor对象锁,所以在synchronized代码块之外调用会出现错误,错误提示为:
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at it.cast.basic.thread.SynchronizedTest.main(SynchronizedTest.java:27)
因为只有拥有了monitor对象的线程才能调用wait()方法进入_WaitSet 等待集合中等待被唤醒,而且对于monitor对象来说,同一时刻只能被一个线程锁持有,在不加synchronized修饰的时候(也就是不是有锁的时候),monitor对象就不会被使用到,这里突然使用wait()方法就是出现逻辑错误,所以必须在synchronized代码块中。
二:wait()、notify()和notifyAll()方法为什么属于Object?
因为每一个对象都可以被当作一把锁来使用,对象在JVM中的内存划分为对象头和实例变量,其中对象头里面有包含了对象的hashcode、锁信息和分代年龄等信息,对象头会根据锁状态的不同,动态的变换,当锁升级为重量级锁的时候,对象头会拥有一个monitor对象的引用,monitor对象也就是每一个对象实现锁的关键,当所有的线程都竞争monitor对象锁,只有一个线程能成功占有锁,其他线程都会进入阻塞进入等待集合中,成功占有锁的线程会接着执行代码,在执行代码的过程中,如果遇到wait()方法,当前线程会释放掉monitor对象锁,然后进入monitor对象的等待被唤醒的集合,唤醒的动作是通过notify()和notifyAll()来实现的。