Synchronized(一)

2020-04-12  本文已影响0人  GableKing黑暗中漫舞

如噩梦一样的考试结束了,让我们剖析一下Synchronized吧😁

为了了解java里这个元老---Synchronized,我们知道他的几种使用方式

public synchronized void SimpleMethod() {
    // code
}
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(long millis)
wait(long millis, int nanos) 

后面两个传入了时间参数(nanos表示纳秒),表示如果指定时间过去还没有其他线程调用notify或者notifyAll方法来将其唤醒,那么该线程会自动被唤醒。

当前线程必须获取到了obj的Monitor,调用其obj.wait(),即wait必须放在同步方法或同步代码块中。执行wait方法后,线程进入等待状态


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)  |
|--------------------------------|-----------------------|------------------------|

对象头的组成:

存储对象自身的运行时数据(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   |
|-------------------------------------------------------|--------------------|

PS:今天不早了,明天继续分析Synchronized,四种锁的状态如何切换🤓

上一篇下一篇

猜你喜欢

热点阅读