如噩梦一样的考试结束了,让我们剖析一下Synchronized吧😁
为了了解java里这个元老---Synchronized,我们知道他的几种使用方式
- 同步普通方法,锁的是当前对象。
public synchronized void SimpleMethod() {
// code
}
- 同步静态方法,锁的是当前 Class 对象。
public static synchronized void staticMethod() {
// code
}
- 同步块,锁的是 () 中的对象。
public void Lock() {
Object o = new Object();
Synchronized (o) {
// code
}
}
写一段简单的代码试试看,底层到底干了什么。
public class TestSynchronize {
public static void main(String[] args) {
synchronized (TestSynchronize.class) {
testSynchronize();
}
}
public synchronized static void testSynchronize() {
System.out.println("hello synchroinze");
}
}
临界区:就是同时只允许一个线程访问的代码区域,那么synchronized修饰代码区域,就是临界区
先用javap看一下,jvm对synchronize的处理
从红色圈圈可以看到,JVM对于synchronize,指令级别增加了monitorenter和moniterexit。
访问synchronize修饰的代码块,通过对象监视器( Monitor )进行获取,而这个获取过程排除了其他线程进入,保证了同一时间只有一个线程来访问。而没有获取到锁的线程将会阻塞到synchronize开始处,直到获取锁的线程 monitor.exit 之后才能尝试继续获取锁。
对象监视器(Monitor)
Java虚拟机给每个对象和class字节码都设置了对象监听器Monitor,每个对象都可以被监视。同时在Object类中还提供了notify和wait方法来对线程进行控制。
Monitor机制:
Monitor保证每次只能有一个线程能进入这个房间进行访问被保护的数据,数据进入房间即为持有Monitor,退出房间即为释放Monitor。
当一个线程需要访问受保护的数据(即需要获取对象的Monitor)时,它会首先在entry-set入口队列中排队,如果没有其他线程正在持有对象的Monitor,那么它会和entry-set队列和wait-set队列中的被唤醒的其他线程进行竞争(即通过CPU调度),选出一个线程来获取对象的Monitor,执行受保护的代码段,执行完毕后释放Monitor,如果已经有线程持有对象的Monitor,那么需要等待其释放Monitor后再进行竞争。
wait-set:当一个线程拥有Monitor后,经过某些条件的判断,这个时候需要调用Object的wait方法,线程就释放了Monitor,进入wait-set队列,等待Object的notify方法。当该对象调用了notify方法 或者notifyAll方法后,wait-set中的线程就会被唤醒,然后在wait-set队列中被唤醒的线程和entry-set队列中的线程一起通过CPU调度来竞争对象的Monitor,最终只有一个线程能获取对象的Monitor。
Object类wait和notify
这里Object提供了Object.wait()和Object.notify()
- wait方法
wait有三个重载方法,分别如下:
wait()
wait(long millis)
wait(long millis, int nanos)
后面两个传入了时间参数(nanos表示纳秒),表示如果指定时间过去还没有其他线程调用notify或者notifyAll方法来将其唤醒,那么该线程会自动被唤醒。
当前线程必须获取到了obj的Monitor,调用其obj.wait(),即wait必须放在同步方法或同步代码块中。执行wait方法后,线程进入等待状态。
-
notify方法
notify:只能唤醒一个正在等待这个对象的monitor的线程
notifyAll:会唤醒所有正在等待这个对象的monitor的线程调用notify方法,必须要等同步代码块结束后才会释放Monitor,必须保证其他线程处于wait状态,否则调用notify没有任何效果。也就是wait和notify/notifyAll必须配合使用。
Java对象头
Synchronized用的锁是存在Java对象头里的,这里就需要了解对象头的结构
java对象头有以下两种(32位JVM):
- 普通对象
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
- 数组对象
|---------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|------------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|
对象头的组成:
- Mark Word
存储对象自身的运行时数据(hashcode,gc分代年龄),大小为JVM一个字的大小,(32bit/32位虚拟机,64bit/64位虚拟机),其中后两位是标记位,标记位不同,这个markword表示的含义不同
biased_lock | lock | 状态 |
---|---|---|
0 | 01 | 无锁 |
1 | 01 | 偏向锁 |
0 | 00 | 轻量级锁 |
0 | 10 | 重量级锁 |
0 | 11 | GC标记 |
不同情况对应的Mark Word如下
|-------------------------------------------------------|--------------------|
| Mark Word (32 bits) | State |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
| | lock:2 | Marked for GC |
|-------------------------------------------------------|--------------------|
-
普通对象
identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程Monitor中。
age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代
biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。 -
偏向锁
thread:持有偏向锁的线程ID。
epoch:偏向时间戳。 -
轻量级锁
ptr_to_lock_record:指向栈中锁记录的指针。 -
重量级锁
ptr_to_heavyweight_monitor:指向管程Monitor的指针。
PS:今天不早了,明天继续分析Synchronized,四种锁的状态如何切换🤓
网友评论