鎖原理及synchronized鎖升級過程


重入鎖

image-20210228191931863

synchronized

image-20210228193405292

 

 

synchronized加到代碼塊上時兩種情況

  1. synchronized(this):表示加鎖的效果如同加到普通方法上 synchronized(this){} = viod synchronized get(){} ;對象鎖:不跨線程保護

  2. synchronized(Test.class):表示加鎖的效果如同加到靜態方法上 synchronized(this){} = static viod synchronized get(){} ;類鎖:跨線程保護

並發:資源共享且互斥

為什么synchronized能夠起到阻塞的效果

synchronized(lock) 中被鎖定的對象的內存布局

image-20210228194914502

image-20210228195203082

jdk1.6 以后 synchronized的功能進行了優化:對鎖的升級規則(無鎖->偏向鎖(cas)-》輕量級鎖(自旋)-》重量級鎖(阻塞))

鎖升級的規則

假如存在線程1、線程2 兩個線程 :

  1. 只有線程1訪問-- 》偏向鎖 (cas 添加線程id)

  2. 線程1和線程2交替訪問 --》輕量級鎖-自旋(不是鎖,只是輪詢cas更換對象的MarkWord)

  3. 兩者同時訪問-》持鎖時間較長(業務處理慢)-》重量級鎖

    無鎖--》偏向鎖

    image-20210228201133245

    偏向鎖--》輕量級鎖

    image-20210228201042998

wait/notify

線程獲取鎖 通過monitorenter(指令)成功后獲得對象鎖,其他線程進入同步隊列,wait的線程釋放鎖進入等待隊列

image-20210228201939647

 

 

 

可見性問題

CPU層面的高速緩存帶來緩存不一致問題--》可見性問題:

cpu層面解決可見性問題 引入了:

總線鎖;緩存鎖

緩存一致性協議(x86:MESI):表示緩存行的四種狀態(會出現指令重排序:亂序執行)

image-20210228203641843

 

 

總結:cpu層面仍然會存在可見性問題(但是提供內存屏障指令)

讀屏障、寫屏障、全屏障

對象頭mark word

我們可以將上面的注釋轉成以下的表格

|-----------------------------------------------------------------------------------------------------------------|
|                                             Object Header(128bits)                                              |
|-----------------------------------------------------------------------------------------------------------------|
|                                   Mark Word(64bits)               |  Klass Word(64bits)    |      State         |
|-----------------------------------------------------------------------------------------------------------------|
| unused:25|identity_hashcode:31|unused:1|age:4|biase_lock:1|lock:2 | OOP to metadata object |      Nomal         |
|-----------------------------------------------------------------------------------------------------------------|
| thread:54|      epoch:2       |unused:1|age:4|biase_lock:1|lock:2 | OOP to metadata object |      Biased        |
|-----------------------------------------------------------------------------------------------------------------|
|                     ptr_to_lock_record:62                 |lock:2 | OOP to metadata object | Lightweight Locked |
|-----------------------------------------------------------------------------------------------------------------|
|                    ptr_to_heavyweight_monitor:62          |lock:2 | OOP to metadata object | Heavyweight Locked |
|-----------------------------------------------------------------------------------------------------------------|
|                                                           |lock:2 | OOP to metadata object |    Marked for GC   |
|-----------------------------------------------------------------------------------------------------------------|

從上面的表格,我們可以看出Java的對象頭在對象的不同的狀態下會有不同的表現形式,主要有三種狀態,無鎖狀態,加鎖狀態,GC標記狀態。那么就可以理解Java當中的上鎖其實可以理解給對象上鎖。也就是改變對象頭的狀態,如果上鎖成功則進入同步代碼塊。但是Java當中的鎖又分為很多種,從上圖可以看出大體分為偏向鎖、輕量鎖、重量鎖三種鎖狀態。這三種鎖的效率是完全不同、關於效率的分析會在下文分析。我們需要查看對象頭,就需要用到借助JOL工具。

首先我們在項目中引入JOL的依賴,具體如下圖:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>

然后創建A.java

public class A{} 然后創建JOLExample1.java

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

import static java.lang.System.out;

public class JOLExample1 {
   static A a;
public static void main(String[] args) {
   a = new A();
   //打印JVM的詳細信息
   out.println(VM.current().details());
   //打印對應的對象頭信息
   out.println(ClassLayout.parseInstance(a).toPrintable());
}
}

運行的結果如下

image-20210321140633664

不是說Klass是64bits(8個字節)但是這兒只有4個字節,是因為我們開啟了指針壓縮,我們可以關閉指針壓縮看看,是不是8個字節。我們只需要使用以下的JVM運行參數

-XX:-UseCompressedOops

再次運行剛才的程序,可以看到我們Klass對象是64bits(16個字節),具體如下圖

image-20210321141124406

看完了對象的實例數據,我們就來到了今天的重頭戲,Java的對象頭(在開啟JVM指針壓縮的情況下是12B),那么這12B存儲的是什么?我們可以看下 OpenJDK的官網的解釋

首先引用openjdk文檔當中對對象頭的解釋

 

上述引用中提到一個java對象頭包含2個word,並且包含了堆對象的布局、類型、GC狀態、同步狀態和標識哈希碼,具體怎么包含的呢?又是哪兩個word呢?

Mark word為第一個word根據文檔可以知道它里面包含了鎖的信息、hashcode、gc信息等等,第二個word是什么呢?

 

klass word 為對象頭的第二個word主要指向對象的元數據。

假設我們理解一個對象頭主要由上圖兩個部分組成(數組對象除外,數組對象的對象還包含一個數組長度),由我們的推導出Mark word是8個字節,klass word(開啟指針壓縮的情況下是4個字節,不開啟的時候是8個字節)。我們打印出來的對象頭是12個字節,所以其中的8個字節是Mark word,剩下的4個字節是klass word,但是和鎖相關的就是Mark word,那么接下來要重點分析Mark word里面信息。

由最開始的64位的表格,我們可以得知在無鎖的情況下Markword當中前56bit存的是對象的hashcode,我們來驗證一下

修改A.java 的代碼如下

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;
public class JOLExample2 {
   public class A {
   //占一個字節的boolean字段
   private boolean flag;
}

新建一個JOLExample2.java具體代碼如下

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;
public class JOLExample2 {
public static void main(String[] args) {
   A a = new A();
   //沒有計算HashCode之前的對象頭
   out.println("before hash");
   out.println(ClassLayout.parseInstance(a).toPrintable());
   
   //jvm計算HashCode
   out.println("jvm----------" + Integer.toHexString(a.hashCode()));
   
   //當計算完HashCode之后,我們可以查看對象頭的信息變化
   out.println("after hash");
   out.println(ClassLayout.parseInstance(a).toPrintable());
   
}
}

運行的結果如下:

在这里插入图片描述

可以看到我們在沒有進行hashcode運算的時候,所有的值都是空的。當我們計算完了hashcode,對象頭就是有了數據。因為是小端存儲,所以你看的值是倒過來的。前25bit沒有使用所以都是0,后面31bit存的hashcode,所以第一個字節中八位存儲的分別就是分代年齡、偏向鎖信息、對象狀態,這8bit分別表示的信息如下圖所示,這個圖會隨着對象的狀態改變而改變,下圖是無鎖的狀態下

在这里插入图片描述

無鎖、偏向鎖、輕量鎖、重量鎖、GC標記( 001,101,00,10,11)

æ— é”çŠ¶æ€ä¸‹çš„å¯¹è±¡å¤´

關於對象狀態一共分為五種狀態,分別是無鎖、偏向鎖、輕量鎖、重量鎖、GC標記

鎖狀態 鎖標識 備注
無鎖 001 對象頭中使用baised_lock + lock 一共3bit來表示無鎖和偏向鎖的
偏向鎖 101 對象頭中使用baised_lock + lock 一共3bit來表示無鎖和偏向鎖的
輕量鎖 00 只用到了lock標識位
重量鎖 10 只用到了lock標識位
GC標志 11 只用到了lock標識位

新建一個JOLExample3.java,代碼如下:

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;

public class JOLExample3 {
static A a;

public static void main(String[] args) throws InterruptedException {
   a = new A();
   out.println("before lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

   sync();

   out.println("after lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());
   
}

private static void sync() {
   synchronized (a) {
       out.println("我不知道要打印什么");
  }
}
}

}

查看運行結果如下:

在这里插入图片描述

上面這個程序只有一個線程去調用sync方法,應該是偏向鎖,但是你會發現輸出的結果(第一個字節)依然是00000001和無鎖的時候一模一樣,其實這是因為虛擬機在啟動的時候對於偏向鎖有延遲,如果沒有偏向鎖的延遲的話,虛擬機在啟動的時候,可能JVM某個線程調用你的線程,這樣就有可能變成了輕量鎖或者重量鎖,所以要做偏向鎖的延遲,那我們怎么看到打印的對象頭是偏向鎖呢?有兩種方式:第一種是加鎖之前先讓線程睡幾秒。第二種加上JVM的運行參數,關閉偏向鎖的延遲,具體的命令如下:

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

第一種方式:修改JOLExample3.java如下

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;

public class JOLExample3 {
static A a;

public static void main(String[] args) throws InterruptedException {
   //切記延遲一定要放在對象創建之前,不然是無效的,因為在你對象創建之前,偏向鎖的延遲的時間
   //沒有給你睡過去,這時候,對象已經創建了,對象頭的信息已經生成了。
   Thread.sleep(5000);
   a = new A();
   out.println("before lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

   sync();

   out.println("after lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

}

private static void sync() {
   synchronized (a) {
       out.println("lock ing");
       out.println(ClassLayout.parseInstance(a).toPrintable());
  }
}

}

再次運行,查看結果如下:

在这里插入图片描述

可以發現已經變成了00000101,偏向鎖,需要注意的after lock,退出同步后依然保持了偏向信息。

第二種方式:利用jvm參數,首先我們先關閉睡眠5秒的,然后運行配置如下:

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

再次運行查看結果如下:

在这里插入图片描述

這時候大家會有疑問了,為什么在沒有加鎖之前是偏向鎖,准確的說,應該是叫可偏向的狀態,因為它后面沒有存線程的ID,當lock ing的時候,后面存儲的就是線程的ID(44969989)既然這兒存儲是線程的ID,那么HashCode又存儲到什么地方去了?是不是計算了HashCode就是不能偏向了?我們來驗證一下,計算完HashCode,還是不是偏向鎖了

我們再次修改JOLExample3.java,具體代碼如下:

 

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;

public class JOLExample3 {
static A a;

public static void main(String[] args) throws InterruptedException {
   //切記延遲一定要放在對象創建之前,不然是無效的,因為在你對象創建之前,偏向鎖的延遲的時間
   //沒有給你睡過去,這時候,對象已經創建了,對象頭的信息已經生成了。
   //Thread.sleep(5000);
   a = new A();
   out.println("before lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());
   a.hashCode();

   sync();

   out.println("after lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

}

private static void sync() {
   synchronized (a) {
       out.println("lock ing");
       out.println(ClassLayout.parseInstance(a).toPrintable());
  }
}
}

同時關閉JVM中偏向鎖的延遲,運行的結果如下:

在这里插入图片描述

我們可以發現:在before lock的時候是可偏向的狀態,lock ing的時候變成了輕量鎖,after lock 的時候變成了無鎖,所以我們得出對象計算了HashCode,就不是偏向鎖了。

看完了偏向鎖的對象頭,我們再來看看輕量鎖的對象頭,輕量級鎖嘗試在應用層面解決線程同步問題,而不觸發操作系統的互斥操作,輕量級鎖減少多線程進入互斥的幾率,不能代替互斥。

創建JOLExample4.java,代碼如下:

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;

public class JOLExample4 {
   static A a;
public static void main(String[] args) {
   a = new A();
   out.println("before lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

   sync();

   out.println("after lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());
}

private static void sync() {
   synchronized (a) {
       out.println("lock ing");
       out.println(ClassLayout.parseInstance(a).toPrintable());
  }
}

}

運行結果如下:

在这里插入图片描述

可以得出:before lock 的時候是 00000001 無鎖的狀態,lock ing 的時候是 01010000 輕量鎖的狀態,after lock 的時候是 00000001 無鎖的狀態。

看完了輕量鎖的對象頭,我們再來看看重量鎖的對象頭,我們先創建一個JOLExample5.java具體代碼如下:

 

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;

public class JOLExample5 {
   static A a;
public static void main(String[] args) throws InterruptedException {
   a = new A();
   out.println("before lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());
   
   Thread t1 = new Thread(()->{
       synchronized (a) {
           try {
               Thread.sleep(5000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           out.println("t1 release");
      }
  });
   t1.start();
   
   Thread.sleep(1000);
   out.println("t1 lock ing");
   out.println(ClassLayout.parseInstance(a).toPrintable());
   
   sync();
   out.println("after lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

   System.gc();
   out.println("after gc()");
   out.println(ClassLayout.parseInstance(a).toPrintable());
}

private static void sync() {
   synchronized (a) {
       out.println("main lock ing");
       out.println(ClassLayout.parseIstance(a).toPrintable());
  }
}}

運行結果如下:

在这里插入图片描述

在加鎖之前(before lock)是 00000001 無鎖,這時候t1來加鎖,因為只有他一個線程所以輕量鎖(t1 lock ing 00010000)由於t1在run方法中睡眠了5秒,這時候主線程也來嘗試加鎖,這個時候就是兩個線程競爭了,所以是重量鎖(main lock ing 00101010)

在这里插入图片描述

當結束的時候,還是重量鎖(afteer lock 00101010),當執行一次gc操作過后發現變成了無鎖但是年齡加了1(after gc() 00001001)

還有一點需要我們注意的就是:當調用wait方法會直接變成重量鎖,我們來驗證一下,創建JOLExample6.java,代碼如下:

 

import org.openjdk.jol.info.ClassLayout;

import static java.lang.System.out;

public class JOLExample6 {
   static A a;
public static void main(String[] args) throws Exception {

   a = new A();
   out.println("before lock");
   out.println(ClassLayout.parseInstance(a).toPrintable());

   Thread t1 = new Thread(() -> {
       try {
           synchronized (a) {
               out.println("before wait");
               out.println(ClassLayout.parseInstance(a).toPrintable());
               a.wait();
               out.println("after wait");
               out.println(ClassLayout.parseInstance(a).toPrintable());
          }
      } catch (InterruptedException e) {
           e.printStackTrace();
      }

  });
   t1.start();
   Thread.sleep(5000);
   synchronized (a) {
       a.notifyAll();
  }
}
}

運行結果如下:

在这里插入图片描述

可以看到在加鎖之前是無鎖的狀態,執行wait方法之前是輕量鎖,執行wait 方法之后,被喚醒的后是重量鎖。

既然synchronized關鍵字有這三種鎖,我們簡單的比較它們之間的性能(粗略的比較下),書寫以下的代碼

public class A {
   int i;
   public synchronized void parse() {
       i++;
  }
}

//關閉偏向鎖延遲‐XX:BiasedLockingStartupDelay=0
public class JOLExample7 {
   public static void main(String[] args) throws Exception {
       A a = new A();
       long start = System.currentTimeMillis();
       //調用同步方法1000000000L 來計算1000000000L的++,對比偏向鎖和輕量級鎖的性能
       //如果不出意外,結果灰常明顯
       for (int i = 0; i < 1000000000L; i++) {
           a.parse();
      }
       long end = System.currentTimeMillis();
       System.out.println(String.format("%sms", end - start));
  }

}

 

先運行加上jvm參數關閉偏向鎖延遲,就是偏向鎖,然后運行的結果如下:

在这里插入图片描述

我們在開啟偏向鎖延遲就是輕量鎖,然后運行結果如下:

在这里插入图片描述

最后我們在看重量鎖,具體代碼如下:

 

public class A {
    int i;
    public synchronized void parse() {
        JOLExample8.countDownLatch.countDown();
        i++;
    }
}



import java.util.concurrent.CountDownLatch;

public class JOLExample8 {
    static CountDownLatch countDownLatch = new CountDownLatch(1000000000);
public static void main(String[] args) throws Exception {
    final A a = new A();

    long start = System.currentTimeMillis();

    //調用同步方法1000000000L 來計算1000000000L的++,對比各種鎖的性能
    //如果不出意外,結果灰常明顯
    for (int i = 0; i < 2; i++) {
        new Thread(() -> {
            while (countDownLatch.getCount() > 0) {
                a.parse();
            }
        }).start();
    }
    countDownLatch.await();
    long end = System.currentTimeMillis();
    System.out.println(String.format("%sms", end - start));
}
}

重量級鎖的執行結果如下:

在这里插入图片描述

最后總結的結果如下:

偏向鎖 輕量鎖 重量鎖
2355ms 23564ms 31227ms

偏向鎖 輕量鎖 重量鎖 2355ms 23564ms 31227ms 最后我們再畫個圖總結下各種鎖的對象頭(只畫出了最重要的部分,其他的省略)

在这里插入图片描述

 

不同情況下的鎖升級策略

證明偏向鎖

證明偏向鎖 證明偏向鎖之前,咱們按下圖操作,給jvm添加查看全局配置的參數:

在这里插入图片描述

直接運行main方法,運行結果如下所示(由於篇幅問題,只截圖了關鍵部分)

在这里插入图片描述

由圖中的-XX:BiasedLockingStartupDelay=4000配置可知,jvm會在啟動虛擬機之后的4s后才會開啟偏向鎖功能。知道這個概念后,咱們再來科普下什么是偏向鎖。 所謂偏向鎖:即當一把鎖處於可偏向狀態時,當有線程持有這把鎖后,這把鎖將偏向於這個線程。這里提到了可偏向狀態,何為可偏向狀態呢?可偏向狀態是指在jvm開啟可偏向功能后,new出來的一個對象它都是可偏向狀態,即它的標識位為101,但是沒有具體的偏向某一個線程。 證明可偏向狀態和偏向鎖: 添加如下代碼並執行:

public class Valid {
public static void main(String[] args) throws InterruptedException {
    // 這里要注意, 一定要在創建對象之前睡眠,若我們先創建對象,可以想一想會發生什么情況!
    // 那肯定是不會啟動偏向鎖的功能呀,我們都知道加鎖其實是給對象加了個標識
    // 如果我們在偏向鎖功能未開啟之前創建了對象,很抱歉,
    // jvm沒有那么智能,后面不會去把這個對象改成可偏向狀態(是偏向鎖,但是沒有偏向具體
    // 的線程)
    Thread.sleep(4100);

	System.out.println(ByteOrder.nativeOrder().toString());
    User user = new User();
    System.out.println("before lock");
    System.out.println(ClassLayout.parseInstance(user).toPrintable());

    synchronized (user) {
        System.out.println("lock ing");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());
    }

    System.out.println("after lock");
    System.out.println(ClassLayout.parseInstance(user).toPrintable());
}
}

查看運行結果

在这里插入图片描述

證明一個對象調用了hashcode方法后無法再被標識為偏向鎖,而是升級成輕量鎖

編寫如下代碼(相對於上述代碼,僅在加鎖前調用了對象的hashcode方法):

public class Valid {

    public static void main(String[] args) throws InterruptedException {
        // 這里要注意, 一定要在創建對象之前睡眠,若我們先創建對象,可以想一想會發生什么情況!
        // 那肯定是不會啟動偏向鎖的功能呀,我們都知道加鎖其實是給對象加了個標識
        // 如果我們在偏向鎖功能未開啟之前創建了對象,很抱歉,
        // jvm沒有那么智能,后面不會去把這個對象改成可偏向狀態(是偏向鎖,但是沒有偏向具體
        // 的線程)
        Thread.sleep(4100);

        System.out.println(ByteOrder.nativeOrder().toString());
        User user = new User();
        System.out.println("before lock");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());

        System.out.println(user.hashCode());

        synchronized (user) {
            System.out.println("lock ing");
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
        }

        System.out.println("after lock");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());
    }
}

在这里插入图片描述

證明輕量鎖

  • 這里說下輕量鎖的概念:若線程是交替執行的,即上一個線程執行完釋放鎖后下一個線程再獲取鎖。若在jvm未開啟偏向鎖的過程中,對對象進行加鎖時,對象直接是輕量鎖。

public class Valid {

    public static void main(String[] args) throws InterruptedException {
        System.out.println(ByteOrder.nativeOrder().toString());

        User user = new User();
        System.out.println("before lock");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());

        synchronized (user) {
            System.out.println("lock ing");
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
        }

        System.out.println("after lock");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());
    }
}

在这里插入图片描述

證明偏向鎖膨脹為輕量鎖

public class Valid {

    public static void main(String[] args) throws InterruptedException {
        // 開啟偏向鎖功能
        Thread.sleep(4100);
        System.out.println(ByteOrder.nativeOrder().toString());

        User user = new User();
        System.out.println("before lock" + ClassLayout.parseInstance(user).toPrintable());

        synchronized (user) {
            System.out.println("lock ing" + ClassLayout.parseInstance(user).toPrintable());
        }

        System.out.println("after lock" + ClassLayout.parseInstance(user).toPrintable());

        // 開啟線程來獲取鎖
        Thread t1 = new Thread(() -> {
            synchronized (user) {
                System.out.println("other t1 thread get lock" + ClassLayout.parseInstance(user).toPrintable());
            }
        }, "t1");
        t1.start();
        // 等待t1執行完后再打印一次鎖信息
        t1.join();

        System.out.println("after t1 thread release lock" + ClassLayout.parseInstance(user).toPrintable());
    }
}

在这里插入图片描述

證明重量鎖

  • 重量鎖概念:多個線程存在激烈的競爭時,鎖會膨脹成重量鎖,且不可逆!

  • 典型案例:生產者消費者模型:

public class ValidSynchronized {

    static Object lock = new Object();

    static volatile LinkedList<String> queue = new LinkedList<>();

    public static void main(String[] args) throws InterruptedException {
        System.out.println("before lock");
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());

        Consumer consumer = new Consumer();
        Producer producer = new Producer();

        consumer.start();
        producer.start();

        Thread.sleep(500);
        consumer.interrupt();
        producer.interrupt();

        // 睡眠3s ==> 目的是為了讓鎖自己釋放,防止在釋放過程中打印鎖的狀態出現重量鎖的情況
        Thread.sleep(3000);
        System.out.println("after lock");
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
    }
}

class Producer extends Thread {

    @Override
    public void run() {
        while (!isInterrupted()) {
            synchronized (ValidSynchronized.lock) {
                System.out.println("lock ing");
                System.out.println(ClassLayout.parseInstance(ValidSynchronized.lock).toPrintable());
                String message = UUID.randomUUID().toString();
                System.out.println("生產者生產消息:" + message);
                ValidSynchronized.queue.offer(message);
                try {
                    // 生產者自己wait,目的是釋放鎖
                    ValidSynchronized.lock.notify();
                    ValidSynchronized.lock.wait();
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    this.interrupt();
                }
            }
        }
    }
}

class Consumer extends Thread {

    @Override
    public void run() {
        while (!isInterrupted()) {
            synchronized (ValidSynchronized.lock) {
                if (ValidSynchronized.queue.size() == 0) {
                    try {
                        ValidSynchronized.lock.wait();
                        ValidSynchronized.lock.notify();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                String message = ValidSynchronized.queue.pollLast();
                System.out.println("消費者消費消息:" + message);

                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    this.interrupt();
                }
            }
        }
    }
}

在这里插入图片描述

證明調用wait方法后,鎖會升級為重量鎖

public class ValidWait {

    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(4100);

        final User user = new User();

        System.out.println("before lock");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());

        Thread t1 = new Thread(() -> {
            synchronized (user) {
                System.out.println("lock ing");
                System.out.println("before wait");
                System.out.println(ClassLayout.parseInstance(user).toPrintable());

                try {
                    user.wait();
                    System.out.println("after wait");
                    System.out.println(ClassLayout.parseInstance(user).toPrintable());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }, "t1");
        t1.start();

        // 主線程睡眠3s后,喚醒t1線程
        Thread.sleep(3000);
        System.out.println("主線程查看鎖,變成了重量鎖");
        System.out.println(ClassLayout.parseInstance(user).toPrintable());
    }
}

在这里插入图片描述

總結

偏向鎖和hashcode是互斥的,只能存在一個。 jvm默認對偏向鎖功能是延遲加載的,大概時間為4s鍾,可以添加JVM參數: -XX:BiasedLockingStartupDelay=0來設置延遲時間為0。偏向鎖的延遲加載關閉后,基本上所有的鎖都會為可偏向狀態,即mark word為101,但是它還沒有具體偏向的線程信息 偏向鎖退出同步塊后依然也是偏向鎖 重量級鎖之所以重量就是因為狀態不停的切換,最終映射到代碼層面就是不停的調用操作系統函數(最終會調用到jvm的mutex類) 調用鎖對象的wait方法時,當前鎖對象會立馬升級為重量級鎖 偏向鎖只要被其他線程拿到了,此時偏向鎖會膨脹。膨脹為輕量鎖

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM