美文网首页
Synchronized(一)

Synchronized(一)

作者: GableKing黑暗中漫舞 | 来源:发表于2020-04-12 16:00 被阅读0次

    如噩梦一样的考试结束了,让我们剖析一下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,四种锁的状态如何切换🤓

    相关文章

      网友评论

          本文标题:Synchronized(一)

          本文链接:https://www.haomeiwen.com/subject/gcivmhtx.html