1. 程式人生 > >Synchronized及其實現原理

Synchronized及其實現原理

synchronized

並發編程中synchronized一直是元老級角色,我們稱之為重量級鎖。主要用在三個地方:

1、修飾普通方法,鎖是當前實例對象。

2、修飾類方法,鎖是當前類的Class對象。

3、修飾代碼塊,鎖是synchronized括號裏面的對象。

一、synchronized實現原理

當一個線程試圖訪問同步代碼塊時,必須得到鎖。在退出或拋出異常時必須釋放鎖,JVM是基於進入和退出Monitor來實現方法同步和代碼塊同步。

我們來看下synchronized的字節碼:

技術分享

public class SynchronizedTest
{    public void addNum1(String userName)
    {
    }    
    public void addNum2(String userName)
    {        synchronized(this)
        {
        }
    }    
    public synchronized void addNum3(String userName)
    {
    }
}

技術分享

在字節碼裏可以看到,用synchronizde修飾的同步代碼塊多了兩個指令:monitorenter、monitorexit;

代碼塊同步是使用monitorenter、monitorexit指令實現的,而方法同步是使用另外一種方式實現的,但是方法同步也可以使用這兩個指令來實現。

monitorenter指令是編譯後插入到同步代碼塊的開始位置,而monitorexit是插入到方法的結束和異常位置。任何一個對象都有一個monitor與之關聯。線程執行到monitorenter指令處時,會嘗試獲取對象所對應的monitor所有權,即嘗試獲得對象的鎖。

二、修飾普通方法 鎖是當前實例對象

我們先來看下將實例對象作為鎖的概念:

技術分享

public class AddNumTest
{    private int num = 0;    
    public synchronized void addNum(String str)
    {        try
        {            if ("a".equals(str))
            {
                num = 10;
                System.out.println("add a");
                Thread.sleep(2000);
            }            else
            {
                num = 20;
                System.out.println("add b");
            }
            System.out.println(str + " num = " + num);
        }        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

技術分享

技術分享

public class AddNumThreadOne implements Runnable
{    private AddNumTest at;    
    public AddNumThreadOne(AddNumTest at)
    {        this.at = at;
    }

    @Override    public void run()
    {
        at.addNum("a");
    }
}

技術分享

技術分享

public class AddNumThreadTwo implements Runnable
{    private AddNumTest at;    public AddNumThreadTwo(AddNumTest at)
    {        this.at = at;
    }
    
    @Override    public void run()
    {
        at.addNum("b");
    }
}

技術分享

技術分享

public class AddNum
{    public static void main(String[] args)
    {        //註意,這裏傳入同一個實例對象
        AddNumTest at = new AddNumTest();        //AddNumTest bt = new AddNumTest();
        Thread t1 = new Thread(new AddNumThreadOne(at));
        Thread t2 = new Thread(new AddNumThreadTwo(at));
        t1.start();
        t2.start();
    }
}

技術分享

執行結果:

add a
a num = 10add b
b num = 20

前面解釋過關鍵字synchronized的實現原理是使用對象的monitor來實現的,取的鎖都是對象鎖,而不是把一段代碼或者函數作為鎖。在並發情況下,如果並發情況下多線程競爭的是同一個對象,那麽先來的獲取該對象鎖,後面的線程只能排隊,等前面的線程執行完畢釋放鎖。

上面介紹的是同一個對象鎖,我們來觀察下獲取不同的對象鎖會是什麽情況:

技術分享

public static void main(String[] args)
    {        //註意,這裏傳入的不同的實例對象
        AddNumTest at = new AddNumTest();
        AddNumTest bt = new AddNumTest();
        Thread t1 = new Thread(new AddNumThreadOne(at));
        Thread t2 = new Thread(new AddNumThreadTwo(bt));
        t1.start();
        t2.start();
    }

技術分享

執行結果:

add a
add b
b num = 20a num = 10

這裏線程1、2搶占的是不同的鎖,盡管線程1先到達同步代碼塊的位置,但是由於monitor不一樣,所以不能阻塞線程2的執行。

三、synchronized鎖重入

鎖重入:當一個線程得到一個對象鎖後,再次請求此對象鎖時可以再次得到該對象的鎖。但是這裏有維護一個計數器,同一個線程每次得到對象鎖計數器都會加1,釋放的時候減1,直到計數器的數值為0的時候,才能被其他線程所搶占

技術分享

public class AgainLock
{    public synchronized void print1()
    {
        System.out.println("do work print1");
        print2();
    }    
    public synchronized void print2()
    {
        System.out.println("do work print2");
        print3();
    }    
    public synchronized void print3()
    {
        System.out.println("do work print3");
    }
}

技術分享

技術分享

public class AgainLockTest
{    public static void main(String[] args)
    {
        Thread t = new Thread(new Runnable()
        {
            @Override            public void run()
            {
                AgainLock al = new AgainLock();
                al.print1();
            }
        });
        t.start();
    }
}

技術分享

執行結果:

do work print1do work print2do work print3

這裏面三個同步方法,使用的鎖都是該實例對象的同步鎖,同一個線程在執行的時候,每次都是在鎖沒有釋放的時候,就要重新再去獲取同一把對象鎖。從運行結果可以看出,關鍵字synchronized支持同一線程鎖重入。

四、synchronized同步代碼塊

用synchronized同步方法的粒度過大,有時候一個方法裏面的業務邏輯很多,但是我們想對同步的部分進行單獨定制,這時候就可以使用synchronized來同步代碼塊。

技術分享

public class SynchronizedTest1
{    public void doWorkTask()
    {        for(int i=0;i<100;i++)
        {
            System.out.println("nosynchronized thread name =" + Thread.currentThread().getName()                + ";i=" + i);
        }        synchronized(this)
        {            for(int i=0;i<100;i++)
            {
                System.out.println("thread name =" + Thread.currentThread().getName()                    + ";i=" + i);
            }
        }
    }
}

技術分享

如果在並發情況下,調用這個類的同一個實例,線程A和B可以同時執行使用synchronized同步之前的代碼邏輯,但是使用關鍵字同步的部分是互斥的,先到達的線程占有對象鎖,後面的線程會被阻塞,直到對象鎖被前面的線程釋放。


Synchronized及其實現原理