JOL探索synchronized鎖-子路老師

豆腐蛋發表於2020-12-06

synchronized鎖:①鎖例項 ②鎖物件

package com.hx.zbhuang;

public class SyncExplore {

    public void test(){
        // 鎖住類例項
        synchronized (this){
            System.out.println("this start");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("this end");
        }
    }

    public synchronized void test1(){
        // 鎖住類例項
        System.out.println("method this");
    }

    public void test2(){
        // 鎖住類物件
        synchronized (this.getClass()){
            System.out.println("Class start");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Class end");
        }
    }

    public synchronized static void test3(){
        // 鎖住類物件
        System.out.println("method Class");
    }
    
    public static void main(String[] args) {
        SyncExplore syncExplore = new SyncExplore();
        new Thread(syncExplore::test,"t1").start();
        new Thread(syncExplore::test1,"t1").start();
        new Thread(syncExplore::test2,"t1").start();
        new Thread(SyncExplore::test3,"t1").start();
    }

}

持有相同鎖的程式碼塊同步執行

synchronized鎖標識為物件頭的執行時後設資料的執行緒持有鎖

http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

每個GC託管堆物件開始處的公共結構。(每個oop指向一個物件頭)包括關於堆物件的佈局、型別、GC狀態、同步狀態和標識雜湊程式碼的基本資訊。由兩個片語成。在陣列中,它後面緊跟一個長度欄位。Java物件和VM內部物件都有一個通用的物件頭格式。

每個物件頭的第一個欄位。通常是一組位域,包括同步狀態和標識雜湊碼。也可以是指向同步相關資訊的指標(具有特徵低位編碼)。在GC期間,可能包含GC狀態位。

每個物件頭的第二個欄位。指向另一個描述原始物件佈局和行為的物件(元物件)。對於java物件,“KLASS”包含C++樣式“VTABLE”。

建立一個物件檢視物件頭資訊:

package com.hx.zbhuang;

public class DouFuDan {
    boolean flag = false;
}
package com.hx.zbhuang;

import sun.misc.Unsafe;
import java.lang.reflect.Field;

/**
 * 物件頭hash由二進位制轉換為十六進位制
 */
public class HashUtil {
    public static void countHash(Object object) throws NoSuchFieldException, IllegalAccessException {
        // 反射獲取theUnsafe屬性(new Unsafe())
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        long hashCode = 0;
        //獲取去除MarkWord前8位(鎖標誌位)後56位二進位制(hashCode)
        for (long index = 7; index > 0; index--) {
            hashCode |= (unsafe.getByte(object, index) & 0xFF) << ((index - 1) * 8);
        }
        //轉換為十六進位制
        String code = Long.toHexString(hashCode);
        System.out.println("util-----------0x"+code);

    }
}
package com.hx.zbhuang;

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

public class JOLExploreSync1 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        DouFuDan douFuDan = new DouFuDan();
        // 獲取jvm資訊
        System.out.println(VM.current().details());
        System.out.println("before hash");
        // 物件進行hashCode
        System.out.println(Integer.toHexString(douFuDan.hashCode()));
        // 獲取物件頭的hashCode值
        HashUtil.countHash(douFuDan);
        System.out.println("after hash");
        // 獲取物件資訊
        System.out.println(ClassLayout.parseInstance(douFuDan).toPrintable());
    }
}

 

可以看出物件由16位儲存,物件頭佔12位,例項資料佔1位,對齊填充站3位,其中物件頭中執行時後設資料佔8位,型別指標佔4位,執行時後設資料中第一位儲存分帶年齡,偏向鎖標識,物件狀態,第二到第五位為hashCode值,第六到第八位為unuse,

第一位儲存:

DouFuDan物件未持有鎖,偏向鎖標識為0,物件狀態為01為無鎖

測試持有鎖:

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class JOLExploreSync2 {
    public static void main(String[] args) {
        DouFuDan douFuDan = new DouFuDan();
        System.out.println("before lock:" + ClassLayout.parseInstance(douFuDan).toPrintable());
        synchronized (douFuDan) {
            System.out.println("locking:" + ClassLayout.parseInstance(douFuDan).toPrintable());
        }
        System.out.println("after lock:" + ClassLayout.parseInstance(douFuDan).toPrintable());    }
}

DouFuDan物件被加鎖後偏向鎖標誌位為0,物件狀態位為00,大佬們說這個是輕量鎖,而非偏向鎖為什麼呢?

(輕量級鎖嘗試在應用層面解決執行緒同步 問題,而不觸發作業系統的互斥操作,輕量級鎖減少多執行緒進入互斥的機率,不能代替互斥)

加個休眠時間

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class JOLExploreSync2 {
    public static void main(String[] args) {
        try {
            Thread.sleep(5000);
            DouFuDan douFuDan = new DouFuDan();
            System.out.println("before lock:" + ClassLayout.parseInstance(douFuDan).toPrintable());
            synchronized (douFuDan) {
                System.out.println("locking:" + ClassLayout.parseInstance(douFuDan).toPrintable());
            }
            System.out.println("after lock:" + ClassLayout.parseInstance(douFuDan).toPrintable());

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

發現偏向鎖標誌位為1,物件狀態位為01,為大佬所說的偏向鎖標識位,這是為什麼呢?

檢視https://www.oracle.com/java/technologies/java-tuning.html#section4.2.5

Enables a technique for improving the performance of uncontended synchronization. An object is "biased" toward the thread which first acquires its monitor via a monitorenter bytecode or synchronized method invocation; subsequent monitor-related operations performed by that thread are relatively much faster on multiprocessor machines. Some applications with significant amounts of uncontended synchronization may attain significant speedups with this flag enabled; some applications with certain patterns of locking may see slowdowns, though attempts have been made to minimize the negative impact.

啟用一種提高無爭用同步效能的技術。一個物件“偏向”於執行緒,該執行緒首先通過monitorenter位元組碼或同步方法呼叫獲取其監視器;在多處理器計算機上,由該執行緒執行的與監視器相關的後續操作相對要快得多。一些具有大量非競爭同步的應用程式在啟用此標誌時可能會獲得顯著的加速;某些具有某些鎖定模式的應用程式可能會出現減速,儘管已經嘗試將負面影響降至最低。

在另一篇探索synchronized偏向鎖與重量鎖區別中修改pthread_mutex_lock發現啟動main程式會有大量執行緒操作os加鎖,如果開始就加上偏向鎖,就會導致減速,可能是jvm底層優化,直接上了輕量鎖

現在我們註釋休眠程式碼,新增引數-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0,將偏向鎖延遲時間設定為0發現結果偏向鎖標誌位為1,物件狀態位為01,應該是jvm做了優化。

鎖競爭測試:

修改DouFuDan

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class DouFuDan {
    public synchronized void lock(){
        System.out.println("method lock locking:" + ClassLayout.parseInstance(this).toPrintable());
    }
}
package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class JOLExploreSync3 {
    public static void main(String[] args) {
        DouFuDan douFuDan = new DouFuDan();
        System.out.println("before lock:" + ClassLayout.parseInstance(douFuDan).toPrintable());
        new Thread() {
            public void run() {
                synchronized (douFuDan) {
                    try {
                        Thread.sleep(6000);
                        System.out.println("main DouFuDan locking:" + ClassLayout.parseInstance(douFuDan).toPrintable());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        System.out.println("after main DouFuDan locking:" + ClassLayout.parseInstance(douFuDan).toPrintable());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        douFuDan.lock();
    }
}

競爭後偏向鎖標誌位為0,物件狀態位為10變為偏向鎖標誌位為0,物件狀態位為10,升級為重量級鎖

檢視http://hg.openjdk.java.net/jdk8u/jdk8u60/hotspot/file/37240c1019fd/src/share/vm/oops/markOop.hpp

發現偏向鎖hashCode位置儲存的是javaThread,這導致計算hashCode之後不能設定為偏向鎖,不然計算出來的hashCode值會被覆蓋為javaThread,導致兩次計算出來的hashCode不一致

休眠五秒後計算hashCode加鎖

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class JOLExploreSync4 {
    public static void main(String[] args) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        DouFuDan douFuDan = new DouFuDan();
        douFuDan.hashCode();
        synchronized (douFuDan) {
            System.out.println("locking:" + ClassLayout.parseInstance(douFuDan).toPrintable());
        }
    }
}

發現偏向鎖標誌位為0,物件狀態位為00,輕量級鎖

總結為

無鎖偏向鎖標誌位為0,物件狀態位為01
偏向鎖偏向鎖標誌位為1,物件狀態位為01
輕量級鎖偏向鎖標誌位為0,物件狀態位為00
重量級鎖偏向鎖標誌位為0,物件狀態位為10

 

 

 

 

 

檢視各種鎖的時間:

修改豆腐蛋

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class DouFuDan {
    int i;
    public synchronized void add(){
        if(i==0) {
            System.out.println("method add"+ ClassLayout.parseInstance(this).toPrintable());
        }
        i++;
    }
}

偏向鎖

package com.hx.zbhuang;

public class JOLExploreSync5 {
    public static void main(String[] args) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        DouFuDan douFuDan = new DouFuDan();
        long start = System.currentTimeMillis();
        for(int i=0;i<1000000000L;i++){
            douFuDan.add();
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
    }
}

輕量級鎖

package com.hx.zbhuang;

public class JOLExploreSync6 {
    public static void main(String[] args) {
        DouFuDan douFuDan = new DouFuDan();
        long start = System.currentTimeMillis();
        for(int i=0;i<1000000000L;i++){
            douFuDan.add();
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
    }
}

重量級鎖

修改DouFuDan

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class DouFuDan {
    public synchronized void open(){
        if(JOLExploreSync7.downLatch.getCount()==0) {
            System.out.println("method add"+ ClassLayout.parseInstance(this).toPrintable());
        }
        JOLExploreSync7.downLatch.countDown();
    }
}
package com.hx.zbhuang;

import java.util.concurrent.CountDownLatch;

public class JOLExploreSync7 {
    static CountDownLatch downLatch = new CountDownLatch(1000000000);
    public static void main(String[] args) {
        DouFuDan douFuDan = new DouFuDan();
        long start = System.currentTimeMillis();
        for(int i=0;i<2;i++){
            new Thread() {
                @Override
                public void run() {
                    while(downLatch.getCount()>0) {
                        douFuDan.open();
                    }
                }
            }.start();
        }
        try {
            downLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
    }
}

鎖型別偏向鎖輕量級鎖重量級鎖
消耗時間/ms46571642564279

 

 

 

wait解鎖

package com.hx.zbhuang;

import org.openjdk.jol.info.ClassLayout;

public class JOLExploreSync8 {
    public static void main(String[] args) {
        DouFuDan douFuDan=new DouFuDan();
        System.out.println("before lock:" + ClassLayout.parseInstance(douFuDan).toPrintable());
        new Thread(){
            @Override
            public void run() {
                synchronized (douFuDan){
                    synchronized (douFuDan){
                        System.out.println("before wait:" + ClassLayout.parseInstance(douFuDan).toPrintable());
                        try {
                            douFuDan.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("after wait:" + ClassLayout.parseInstance(douFuDan).toPrintable());
                    }
                }
            }
        }.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (douFuDan){
            System.out.println("update:" + ClassLayout.parseInstance(douFuDan).toPrintable());
            douFuDan.notify();
        }
    }
}

開始執行緒啟動為輕量級鎖,執行await進行等待釋放鎖,主執行緒執行獲取鎖升級為重量鎖並喚醒子執行緒,後面得到的鎖為重量鎖。

 

相關文章