java-鎖膨脹的過程

先來看個奇怪的demo

public class A {
    int i=0;
   // boolean flag =false;
    public synchronized void parse(){
        i++;
        JOLExample6.countDownLatch.countDown();
    }
}

睡眠5秒,測試

public class JOLExample3 {
   static A a;
    public static void main(String[] args) throws Exception {
        Thread.sleep(5000);
        a= new A();
        //a.hashCode();
        out.println("befor lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());//無鎖:偏向鎖?
        synchronized (a){
            out.println("lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }

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

我注釋的那行程式碼是什麼鎖?看下結果

 可以看出,沒有執行緒持有鎖的時候,是可偏向狀態

 

 然後我們把睡眠的程式碼注釋掉,再測試一下

//Thread.sleep(5000);

看下結果

 再看個兩個執行緒的demo

首先是兩個執行緒交替執行:

public class JOLExample10 {
   static A a;
    public static void main(String[] args) throws Exception {
        a= new A();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    out.println("t1 lock ing");
                    out.println(ClassLayout.parseInstance(a).toPrintable());
                }
            }
        };
        t1.start();
       Thread.sleep(10000);//睡眠10秒,讓main執行緒和t1執行緒交替執行
        synchronized (a){//a b c c+++
            out.println("main lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

看下結果

 

 可以看出,交替執行時,是輕量鎖

我們把睡眠的程式碼注釋掉

//Thread.sleep(5000);//睡眠10秒,讓main執行緒和t1執行緒交替執行

再次測試,

public class JOLExample10 {
   static A a;
    public static void main(String[] args) throws Exception {
        a= new A();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    out.println("t1 lock ing");
                    out.println(ClassLayout.parseInstance(a).toPrintable());
                }
            }
        };
        t1.start();
       //Thread.sleep(5000);//睡眠10秒,讓main執行緒和t1執行緒交替執行
        synchronized (a){//a b c c+++
            out.println("main lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
        Thread.sleep(5000);//睡眠10秒
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
}

 

看下結果

 

 

自旋

自旋一段時間,可以理解為空轉,時間很短,具體時間需要看jvm源碼,如果在自旋時間內拿到了鎖,就不再膨脹,如果還是拿不到鎖,則膨脹為重量鎖,如下

public static void main(String[] args) throws Exception {
        a= new A();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    out.println("t1 lock ing");
                    out.println(ClassLayout.parseInstance(a).toPrintable());
                }
            }
        };
        t1.start();
       Thread.sleep(1670);//睡眠10秒,讓main執行緒和t1執行緒交替執行
        synchronized (a){//自旋一段時間,可以理解為時間很短,具體時間需要看jvm源碼,如果在自旋時間內拿到了鎖,就不再膨脹,如果還是拿不到鎖,則膨脹為重量鎖
            out.println("main lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }

        Thread thread2 = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    out.println("t2 lock ing");
                    out.println(ClassLayout.parseInstance(a).toPrintable());
                }
            }
        };
        thread2.start();
        /*Thread.sleep(10);
        synchronized (a){
            out.println("main lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }*/
        //Thread.sleep(5000);//睡眠10秒,讓main執行緒和t1執行緒交替執行
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }

如果調用wait方法,則立即變成重量鎖

看下demo

public class JOLExample11 {
    static A a;
    public static void main(String[] args) throws Exception {
        //Thread.sleep(5000);
        a = new A();
        out.println("befre lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());

        Thread t1= new Thread(){
            public void run() {
                synchronized (a){
                    try {
                        synchronized (a) {
                            System.out.println("before wait");
                            out.println(ClassLayout.parseInstance(a).toPrintable());
                            a.wait();//如果調用wait方法,則立即變成重量鎖
                            System.out.println(" after wait");
                            out.println(ClassLayout.parseInstance(a).toPrintable());
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        Thread.sleep(7000);
        synchronized (a) {
            a.notifyAll();
        }
    }
}

看下結果

 

 我們再看個synchronized膨脹的奇怪特性

讓偏向鎖無延遲啟動

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

 

public class JOLExample12 {
    static List<A> list = new ArrayList<A>();
    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread() {
            public void run() {
                for (int i=0;i<10;i++){
                    A a = new A();
                    synchronized (a){
                        System.out.println("111111");
                        list.add(a);
                    }
                }

            }

        };
        t1.start();
        t1.join();
        out.println("befre t2");
        //偏向
        out.println(ClassLayout.parseInstance(list.get(1)).toPrintable());
        Thread t2 = new Thread() {
            int k=0;
            public void run() {
                for(A a:list){
                   synchronized (a){
                       System.out.println("22222");
                       if (k==4){
                           out.println("t2 ing");
                           //輕量鎖
                           out.println(ClassLayout.parseInstance(a).toPrintable());

                       }
                   }
                    k++;
                }

            }
        };
        t2.start();
    }
}

t1執行緒new10個對象,t2執行緒取第五個,看下結果

 

 我們把new對象的數量改一下,改成20個,再來試一下

public class JOLExample12 {
    static List<A> list = new ArrayList<A>();
    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread() {
            public void run() {
                for (int i=0;i<20;i++){
                    A a = new A();
                    synchronized (a){
                        System.out.println("111111");
                        list.add(a);
                    }
                }

            }

        };
        t1.start();
        t1.join();
        out.println("befre t2");
        //偏向
        out.println(ClassLayout.parseInstance(list.get(1)).toPrintable());
        Thread t2 = new Thread() {
            int k=0;
            public void run() {
                for(A a:list){
                   synchronized (a){
                       System.out.println("22222");
                       if (k==19){
                           out.println("t2 ing");
                           //輕量鎖
                           out.println(ClassLayout.parseInstance(a).toPrintable());

                       }
                   }
                    k++;
                }

            }
        };
        t2.start();
    }
}

這裡我們取第20個對象,查看對象頭資訊

 

 我們可以看到,居然重偏向了,這裡是jvm做的優化,20次以後就會沖偏向,小於20次時膨脹為輕量鎖

這裡我們稱之為批量偏向,下面我們看下這個的原理

 

 最後總結一下:輕量鎖釋放的時候將mark word重置為無鎖狀態,附上網路上的圖