Java 多線程共用模型之管程(上)

来源:https://www.cnblogs.com/lcha-coding/archive/2022/06/06/16349444.html
-Advertisement-
Play Games

主線程與守護線程 預設情況下,Java 進程需要等待所有線程都運行結束,才會結束。有一種特殊的線程叫做守護線程,只要其它非守護線程運行結束了,即使守護線程的代碼沒有執行完,也會強制結束。 package Daemon; import lombok.extern.slf4j.Slf4j; @Slf4j ...


主線程與守護線程

預設情況下,Java 進程需要等待所有線程都運行結束,才會結束。有一種特殊的線程叫做守護線程,只要其它非守護線程運行結束了,即使守護線程的代碼沒有執行完,也會強制結束。

package Daemon;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo1")
public class demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    break;
                }
            }
            log.debug("結束");
        }, "t1");
        t1.setDaemon(true);
        t1.start();

        Thread.sleep(1000);
        log.debug("結束");
    }
}

輸出:

15:08:26 [main] c.demo1 - 結束

Process finished with exit code 0

註意

  • 垃圾回收器線程就是一種守護線程
  • Tomcat 中的 Acceptor 和 Poller 線程都是守護線程,所以 Tomcat 接收到 shutdown 命令後,不會等待它們處理完當前請求

五種狀態

這是從 操作系統 層面來描述的

  • 【初始狀態】僅是在語言層面創建了線程對象,還未與操作系統線程關聯
  • 【可運行狀態】(就緒狀態)指該線程已經被創建(與操作系統線程關聯),可以由 CPU 調度執行
  • 【運行狀態】指獲取了 CPU 時間片運行中的狀態
    • 當 CPU 時間片用完,會從【運行狀態】轉換至【可運行狀態】,會導致線程的上下文切換
  • 【阻塞狀態】
    • 如果調用了阻塞 API,如 BIO 讀寫文件,這時該線程實際不會用到 CPU,會導致線程上下文切換,進入【阻塞狀態】
    • 等 BIO 操作完畢,會由操作系統喚醒阻塞的線程,轉換至【可運行狀態】
    • 與【可運行狀態】的區別是,對【阻塞狀態】的線程來說只要它們一直不喚醒,調度器就一直不會考慮調度它們
  • 【終止狀態】表示線程已經執行完畢,生命周期已經結束,不會再轉換為其它狀態

六種狀態

這是從 Java API 層面來描述的

  • NEW 線程剛被創建,但是還沒有調用 start() 方法
  • RUNNABLE 當調用了 start() 方法之後,註意,Java API 層面的 RUNNABLE 狀態涵蓋了 操作系統 層面的【可運行狀態】、【運行狀態】和【阻塞狀態】(由於 BIO 導致的線程阻塞,在 Java 里無法區分,仍然認為是可運行)
  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 層面對【阻塞狀態】的細分
  • TERMINATED 當線程代碼運行結束

共用模型之管程

共用帶來的問題

兩個線程對初始值為 0 的靜態變數一個做自增,一個做自減,各做 5000 次,結果是 0 嗎?

package gc;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo1")
public class demo1 {

    static int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for(int i=0;i<5000;i++){
                counter++;
            }
        },"t1");

        Thread t2 = new Thread(() -> {
            for(int i=0;i<5000;i++){
                counter--;
            }
        },"t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",counter);
    }
}

輸出:

16:03:58 [main] c.demo1 - -1238

問題分析

以上的結果可能是正數、負數、零。為什麼呢?因為 Java 中對靜態變數的自增,自減並不是原子操作,要徹底理解,必須從位元組碼來進行分析

例如對於 i++ 而言(i 為靜態變數),實際會產生如下的 JVM 位元組碼指令:

getstatic i // 獲取靜態變數i的值
iconst_1 // 準備常量1
iadd // 自增
putstatic i // 將修改後的值存入靜態變數i

而 Java 的記憶體模型如下,完成靜態變數的自增,自減需要在主存和工作記憶體中進行數據交換:

臨界區

  • 一個程式運行多個線程本身是沒有問題的
  • 問題出在多個線程訪問共用資源
    • 多個線程讀共用資源其實也沒有問題
    • 在多個線程對共用資源讀寫操作時發生指令交錯,就會出現問題
  • 一段代碼塊內如果存在對共用資源的多線程讀寫操作,稱這段代碼塊為臨界區

競態條件

多個線程在臨界區內執行,由於代碼的執行序列不同而導致結果無法預測,稱之為發生了競態條件

synchronized 解決方案

使用阻塞式的解決方案:synchronized,來解決上述問題,即俗稱的【對象鎖】,它採用互斥的方式讓同一時刻至多只有一個線程能持有【對象鎖】,其它線程再想獲取這個【對象鎖】時就會阻塞住。這樣就能保證擁有鎖的線程可以安全的執行臨界區內的代碼,不用擔心線程上下文切換

註意

雖然 java 中互斥和同步都可以採用 synchronized 關鍵字來完成,但它們還是有區別的:

  • 互斥是保證臨界區的競態條件發生,同一時刻只能有一個線程執行臨界區代碼
  • 同步是由於線程執行的先後、順序不同、需要一個線程等待其它線程運行到某個點
package gc;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo1")
public class demo1 {

    static int counter = 0;
    static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for(int i=0;i<5000;i++){
                synchronized (lock){
                    counter++;
                }
            }
        },"t1");

        Thread t2 = new Thread(() -> {
            for(int i=0;i<5000;i++){
                synchronized (lock){
                    counter--;
                }
            }
        },"t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",counter);
    }
}

輸出:

16:14:15 [main] c.demo1 - 0

改進:由面向過程改為面向對象

package gc;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo1")
public class demo1 {
    public static void main(String[] args) throws InterruptedException {
        Room room = new Room();
        Thread t1 = new Thread(() -> {
            for(int i=0;i<5000;i++){
                room.increment();
            }
        },"t1");

        Thread t2 = new Thread(() -> {
            for(int i=0;i<5000;i++){
                room.decrement();
            }
        },"t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",room.getCounter());
    }
}

class Room{
    private int counter = 0;

    public void increment(){
        synchronized (this){
            counter++;
        }
    }

    public void decrement(){
        synchronized (this){
            counter--;
        }
    }

    public int getCounter(){
        synchronized (this){
            return counter;
        }
    }
}

輸出:

16:18:22 [main] c.demo1 - 0

方法上的 synchronized

  • 加在成員方法上,鎖住的是 this
  • 加在靜態方法上,鎖住的是 類名.class
class Test{
 	public synchronized void test() {
 
 	}
}

等價於
class Test{
 	public void test() {
 		synchronized(this) {
 
 		}
 	}
}
class Test{
 	public synchronized static void test() {
 	
 	}
}

等價於
class Test{
 	public static void test() {
 		synchronized(Test.class) {
 
 		}
 	}
}

變數的線程安全分析

成員變數和靜態方法是否線程安全

  • 如果它們沒有共用,則線程安全
  • 如果它們被共用了,根據它們的狀態是否能夠改變,又分兩種情況
    • 如果只有讀操作,則線程安全
    • 如果有讀寫操作,則這段代碼是臨界區,需要考慮線程安全

局部變數是否線程安全

public static void test1() {
 	int i = 10;
 	i++; 
}

每個線程調用 test1() 方法時局部變數 i,會在每個線程的棧幀記憶體中被創建多份,因此不存在共用,所以局部變數是線程安全的

常見的線程安全類

  • String
  • Integer
  • StringBuffffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的類

這裡說它們是線程安全的是指,多個線程調用它們同一個實例的某個方法時,是線程安全的。也可以理解為

Hashtable table = new Hashtable();

new Thread(()->{
 	table.put("key", "value1");
}).start();

new Thread(()->{
 	table.put("key", "value2");
}).start();
  • 它們的每個方法是原子的
  • 註意它們多個方法的組合不是原子的

不可變類線程安全性

String、Integer 等都是不可變類,因為其內部的狀態不可以改變,因此它們的方法都是線程安全的

Monitor

Java 對象頭

(以 32 位虛擬機為例)

  • 普通對象

    Klass Word:是一個指針,指向該對象從屬的 class 類

  • 數組對象

其中 Mark Word 結構為:

  • hashcode:每個對象的哈希碼
  • age:垃圾回收時用到的分代年齡
  • biased_lock:是否是偏向鎖
  • (01,00,10,11):表示加鎖狀態

Monitor(鎖)

過程:

  1. 首先用一個指針試圖將 obj 對象與操作系統中的 Monitor 對象關聯。在正常狀態下,Mark Word 存儲了 hashcode,age等信息,並且加鎖狀態碼為 “01” 表示並未與任何鎖關聯。但是一旦獲得了鎖,加鎖狀態碼會改為 “10” 並且拋棄掉存儲的 hashcode ,age 等信息,轉而存儲一個指向 Monitor 對象的指針(ptr_to_heavyweight_monitor),占30位

  2. 此時線程 Thread-2 指向 Monitor 中的 Owner 表示自己是這把鎖現在的主人

  3. 當一個新的線程到來時(Thread-1),會先去檢查此對象有沒有關聯 Monitor 對象,發現已經關聯,繼而檢查 Monitor 對象中的 Owner 已經是 Thread-2 了,此時 Thread-1會跟Monitor 中的 EntryList(阻塞隊列) 關聯,進入 BLOCK 狀態

  4. Thread-2 執行完同步代碼塊的內容,然後喚醒 EntryList 中等待的線程來競爭鎖,競爭的時是非公平的

synchronized 優化原理

輕量級鎖

輕量級鎖的使用場景:如果一個對象雖然有多線程要加鎖,但加鎖的時間是錯開的(也就是沒有競爭),那麼可以使用輕量級鎖來優化。

輕量級鎖對使用者是透明的,即語法仍然是 synchronized

假設有兩個方法同步塊,利用同一個對象加鎖

static final Object obj = new Object();
public static void method1() {
 	synchronized( obj ) {
 		// 同步塊 A
 		method2();
 	}
}
public static void method2() {
 	synchronized( obj ) {
 		// 同步塊 B
 	}
}
  • 創建鎖記錄(Lock Record)對象,每個線程都的棧幀都會包含一個鎖記錄的結構,內部可以存儲鎖定對象的 Mark Word

  • 讓鎖記錄中 Object reference 指向鎖對象,並嘗試用 cas 替換 Object 的 Mark Word,將 Mark Word 的值存入鎖記錄

  • 如果 cas 替換成功,對象頭中存儲了 鎖記錄地址和狀態 00 ,表示由該線程給對象加鎖,這時圖示如下

  • 如果 cas 失敗,有兩種情況

    • 如果是其它線程已經持有了該 Object 的輕量級鎖,這時表明有競爭,進入鎖膨脹過程

    • 如果是自己執行了 synchronized 鎖重入,那麼再添加一條 Lock Record 作為重入的計數

  • 當退出 synchronized 代碼塊(解鎖時)如果有取值為 null 的鎖記錄,表示有重入,這時重置鎖記錄,表示重入計數減一

  • 當退出 synchronized 代碼塊(解鎖時)鎖記錄的值不為 null,這時使用 cas 將 Mark Word 的值恢復給對象頭

    • 成功,則解鎖成功
    • 失敗,說明輕量級鎖進行了鎖膨脹或已經升級為重量級鎖,進入重量級鎖解鎖流程

鎖膨脹

如果在嘗試加輕量級鎖的過程中,CAS 操作無法成功,這時一種情況就是有其它線程為此對象加上了輕量級鎖(有競爭),這時需要進行鎖膨脹,將輕量級鎖變為重量級鎖。

static Object obj = new Object();
public static void method1() {
 	synchronized( obj ) {
 		// 同步塊
 	}
}
  • 當 Thread-1 進行輕量級加鎖時,Thread-0 已經對該對象加了輕量級鎖

  • 這時 Thread-1 加輕量級鎖失敗,進入鎖膨脹流程

    • 即為 Object 對象申請 Monitor 鎖,讓 Object 指向重量級鎖地址

    • 然後自己進入 Monitor 的 EntryList BLOCKED

  • 當 Thread-0 退出同步塊解鎖時,使用 cas 將 Mark Word 的值恢復給對象頭,失敗。這時會進入重量級解鎖流程,即按照 Monitor 地址找到 Monitor 對象,設置 Owner 為 null,喚醒 EntryList 中 BLOCKED 線程

自旋優化

重量級鎖競爭的時候,還可以使用自旋來進行優化,如果當前線程自旋成功(即這時候持鎖線程已經退出了同步塊,釋放了鎖),這時當前線程就可以避免阻塞。

  • 自旋會占用 CPU 時間,單核 CPU 自旋就是浪費,多核 CPU 自旋才能發揮優勢。
  • 在 Java 6 之後自旋鎖是自適應的,比如對象剛剛的一次自旋操作成功過,那麼認為這次自旋成功的可能性會高,就多自旋幾次;反之,就少自旋甚至不自旋,總之,比較智能。
  • Java 7 之後不能控制是否開啟自旋功能

偏向鎖

輕量級鎖在沒有競爭時(就自己這個線程),每次重入仍然需要執行 CAS 操作。

Java 6 中引入了偏向鎖來做進一步優化:只有第一次使用 CAS 將線程 ID 設置到對象的 Mark Word 頭,之後發現這個線程 ID 是自己的就表示沒有競爭,不用重新 CAS。以後只要不發生競爭,這個對象就歸該線程所有

例如:

static final Object obj = new Object();
public static void m1() {
 	synchronized( obj ) {
 		// 同步塊 A
 		m2();
 	}
}
public static void m2() {
 	synchronized( obj ) {
 		// 同步塊 B
 		m3();
 	}
}
public static void m3() {
 	synchronized( obj ) {
  		// 同步塊 C
 	}
}
偏向狀態

一個對象創建時:

  • 如果開啟了偏向鎖(預設開啟),那麼對象創建後,markword 值為 0x05 即最後 3 位為 101,這時它的thread、epoch、age 都為 0
  • 偏向鎖是預設是延遲的,不會在程式啟動時立即生效,如果想避免延遲,可以加 VM 參數 -XX:BiasedLockingStartupDelay=0 來禁用延遲
  • 如果沒有開啟偏向鎖,那麼對象創建後,markword 值為 0x01 即最後 3 位為 001,這時它的 hashcode、age 都為 0,第一次用到 hashcode 時才會賦值

1) 測試延遲特性

2) 測試偏向鎖

class Dog {}

利用 jol 第三方工具來查看對象頭信息

// 添加虛擬機參數 -XX:BiasedLockingStartupDelay=0 
public static void main(String[] args) throws IOException {
 	Dog d = new Dog();
 	ClassLayout classLayout = ClassLayout.parseInstance(d);
 
    new Thread(() -> {
 		log.debug("synchronized 前");
 		System.out.println(classLayout.toPrintableSimple(true));
 		synchronized (d) {
 			log.debug("synchronized 中");
 			System.out.println(classLayout.toPrintableSimple(true));
 		}
 		log.debug("synchronized 後");
 		System.out.println(classLayout.toPrintableSimple(true));
 	}, "t1").start();
}

輸出:

11:08:58.117 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
11:08:58.121 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101 
11:08:58.121 c.TestBiased [t1] - synchronized 後
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101

3)測試禁用

在上面測試代碼運行時在添加 VM 參數 -XX:-UseBiasedLocking 禁用偏向鎖

輸出:

11:13:10.018 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
11:13:10.021 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 00010100 11110011 10001000 
11:13:10.021 c.TestBiased [t1] - synchronized 後
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

4)測試 hashCode

正常狀態對象一開始是沒有 hashCode 的,第一次調用才生成

撤銷 - 調用對象 hashCode

調用了對象的 hashCode,但偏向鎖的對象 MarkWord 中存儲的是線程 id,如果調用 hashCode 會導致偏向鎖被撤銷

  • 輕量級鎖會在鎖記錄中記錄 hashCode
  • 重量級鎖會在 Monitor 中記錄 hashCode

在調用 hashCode 後使用偏向鎖,記得去掉 -XX:-UseBiasedLocking

輸出:

11:22:10.386 c.TestBiased [main] - 調用 hashCode:1778535015 
11:22:10.391 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001 
11:22:10.393 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 11000011 11110011 01101000 
11:22:10.393 c.TestBiased [t1] - synchronized 後
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001
撤銷 - 其他線程使用對象

當有其它線程使用偏向鎖對象時,會將偏向鎖升級為輕量級鎖

	private static void test2() throws InterruptedException {
        Dog d = new Dog();
        Thread t1 = new Thread(() -> {
            synchronized (d) {
                log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
            synchronized (TestBiased.class) {
                TestBiased.class.notify();
            }
            // 如果不用 wait/notify 使用 join 必須打開下麵的註釋
            // 因為:t1 線程不能結束,否則底層線程可能被 jvm 重用作為 t2 線程,底層線程 id 是一樣的
             /*try {
             System.in.read();
             } catch (IOException e) {
             e.printStackTrace();
             }*/
        }, "t1");
        t1.start();
        Thread t2 = new Thread(() -> {
            synchronized (TestBiased.class) {
                try {
                    TestBiased.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
            synchronized (d) {
                log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        }, "t2");
        t2.start();
    }

輸出:

[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 
[t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 
[t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000 
[t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
撤銷 - 調用 wait / notify
public static void main(String[] args) throws InterruptedException {
        Dog d = new Dog();
        Thread t1 = new Thread(() -> {
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
            synchronized (d) {
                log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
                try {
                    d.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
        }, "t1");
        t1.start();
        new Thread(() -> {
            try {
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (d) {
                log.debug("notify");
                d.notify();
            }
        }, "t2").start();
    }

輸出:

[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101 
[t2] - notify 
[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010
批量重偏向

如果對象雖然被多個線程訪問,但沒有競爭,這時偏向了線程 T1 的對象仍有機會重新偏向 T2,重偏向會重置對象的 Thread ID

當撤銷偏向鎖閾值超過 20 次後,jvm 會這樣覺得,我是不是偏向錯了呢,於是會在給這些對象加鎖時重新偏向至加鎖線程

private static void test3() throws InterruptedException {
        Vector<Dog> list = new Vector<>();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 30; i++) {
                Dog d = new Dog();
                list.add(d);
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
                }
            }
            synchronized (list) {
                list.notify();
            }
        }, "t1");
        t1.start();

        Thread t2 = new Thread(() -> {
            synchronized (list) {
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("===============> ");
            for (int i = 0; i < 30; i++) {
                Dog d = list.get(i);
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
        }, "t2");
        t2.start();
    }

輸出:

[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - ===============> 
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
批量撤銷

當撤銷偏向鎖閾值超過 40 次後,jvm 會這樣覺得,自己確實偏向錯了,根本就不該偏向。於是整個類的所有對象都會變為不可偏向的,新建的對象也是不可偏向的

static Thread t1,t2,t3;
    private static void test4() throws InterruptedException {
        Vector<Dog> list = new Vector<>();
        int loopNumber = 39;
        t1 = new Thread(() -> {
            for (int i = 0; i < loopNumber; i++) {
                Dog d = new Dog();
                list.add(d);
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
                }
            }
            LockSupport.unpark(t2);
        }, "t1");
        t1.start();
        t2 = new Thread(() -> {
            LockSupport.park();
            log.debug("===============> ");
            for (int i = 0; i < loopNumber; i++) {
                Dog d = list.get(i);
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
            LockSupport.unpark(t3);
        }, "t2");
        t2.start();
        t3 = new Thread(() -> {
            LockSupport.park();
            log.debug("===============> ");
            for (int i = 0; i < loopNumber; i++) {
                Dog d = list.get(i);
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
        }, "t3");
        t3.start();
        t3.join();
        log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
    }

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 ​ 將本地存儲的事件數據同步到伺服器,然後經過服務端的存儲、抽取、分析和展示,充分發揮數據真正的價值。 一、數據同步 第一步:在 SensorsSDK 項目中,新增 SensorsAnalyticsNetwork 工具類,並新增 serverURL 用於保存伺服器 URL 地址 #import ...
  • 標簽+元素 1.標題標簽 段落標簽<h1> 一級標題 <h1><h2> 二級標題 <h2><h3> 三級標題 <h3><h4> 四級標題 <h4><h5> 五級標題 <h5><h6> 六級標題 <h6> 2.段落標簽 <p> 我是一個段落標簽 </p> //不換行 3.容器標簽 <div> 這是頭部 ...
  • props中的children屬性 組件標簽只用有子節點的時候,props就會有該屬性; children的屬性跟props一樣的,值可以是任意值;(文本,React元素,組件,函數) 組件: <ClassCom> 傳遞的數據 </ClassCom> 這樣的組件標簽中就會有子節點 props中的ch ...
  • Srinath,科學家,軟體架構師。Apache Axis2項目的聯合創始人,Apache Software基金會的成員,WSO2流處理器(wso2.com/analytics)的聯席架構師。 Srinath通過不懈的努力最終總結出了30條架構原則,他主張架構師的角色應該由開發團隊本身去扮演,而不... ...
  • 在《clickhouse專欄》上一篇文章中《資料庫、數據倉庫之間的區別與聯繫》,我們介紹了什麼是資料庫,什麼是數據倉庫,二者的區別聯繫。clickhouse的定位是“數據倉庫”,所以理解了上一篇的內容,其實就能夠知道clickhouse適用於什麼樣的應用場景,不適合什麼樣的應用場景。 下麵本節我們就 ...
  • 庫 是一種代碼的二進位的封裝形式,將.o文件打包封裝就成了庫。庫可以在任何地方使用,但用戶卻不能看見他的具體實現。庫有利於代碼模塊化,只要介面設計得合理,改變庫的內部實現,不會影響到用戶級別的代碼使用。 動態庫 1.封裝動態庫 假設有源代碼sum.c, sub.c gcc sum.c -c -o s ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • Spring Ioc源碼分析系列--實例化Bean的幾種方法 前言 前面的文章Spring Ioc源碼分析系列--Bean實例化過程(二)在講解到bean真正通過那些方式實例化出來的時候,並沒有繼續分析了,而是留到了這裡去分析,主要是因為獲取獲取構造函數,推斷構造函數也是一個比較複雜的操作,就想另起 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...