java併發synchronized的原理和應用

java併發編程這個領域中synchronized關鍵字一直都是元老級的角色,在java早期版本中,synchronized屬於重量級鎖,效率低下,由於監視器鎖(monitor)是依賴於底層的操做系統的Mutex Lock來實現的,java的線程是映射到操做系統的原生線程之上的。若是要掛起或者喚醒一個線程,都須要操做系統幫忙完成,而操做系統實現線程之間的切換時須要從用戶態轉換到內核態,這個狀態之間的轉換須要相對比較長的時間,時間成本相對較高。在JDK1.6以後java官方對從JVM層面對synchronized 較大優化,因此如今的synchronized鎖效率也優化得很不錯。JDK1.6對鎖的實現引入了大量的優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減小鎖操做的開銷。java

synchronized實現原理

經過反編譯下面的代碼來看看Synchronized是如何實現對代碼塊進行同步的,切換到類的對應目錄執行javac SynchronizedTest.java命令生成編譯後的.class文件,而後執行javap -v SynchronizedTest.class。編程

1)synchronized同步代碼塊併發

public class SynchronizedTest {

    public static void main(String[] args) {
        new SynchronizedTest().method();
    }

    public void method() {
        synchronized (this) {
            System.out.println("synchronized 代碼塊");
        }
    }
}

圖片描述

從上面咱們能夠看出:synchronized 同步語句塊的實現使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結束位置。 當執行 monitorenter 指令時,線程試圖獲取鎖也就是獲取 monitor(monitor對象存在於每一個Java對象的對象頭中,synchronized 鎖即是經過這種方式獲取鎖的,也是爲何Java中任意對象能夠做爲鎖的緣由) 的持有權.當計數器爲0則能夠成功獲取,獲取後將鎖計數器設爲1也就是加1。相應的在執行 monitorexit 指令後,將鎖計數器設爲0,代表鎖被釋放。若是獲取對象鎖失敗,那當前線程就要阻塞等待,直到鎖被另一個線程釋放爲止。異步

synchronized的語義底層是經過一個monitor的對象來完成,其實wait/notify等方法也依賴於monitor對象,這就是爲何只有在同步的塊或者方法中才能調用wait/notify等方法,不然會拋出java.lang.IllegalMonitorStateException的異常的緣由。ide

2)synchronized修飾方法優化

public class SynchronizedTest {

    public static void main(String[] args) {
        new SynchronizedTest().method();
    }

    public synchronized void method() {
        System.out.println("Hello World!");
    }
}

圖片描述

synchronized 修飾的方法的同步並無 monitorenter 指令和 monitorexit 指令完成(理論上其實也能夠經過這兩條指令來實現),不過相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據該標示符來實現方法的同步的:當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,若是設置了,執行線程將先獲取monitor,獲取成功以後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其餘任何線程都沒法再得到同一個monitor對象。 其實本質上沒有區別,只是方法的同步是一種隱式的方式來實現,無需經過字節碼來完成。this

小結:對於同步塊的實現使用了monitorenter和monitorexit指令,而同步方法則是依靠方法修飾符上的ACC_SYNCHRONIZED來完成的。不管採用哪一種方法,其本質是對一個對象的監視器(monitor)進行獲取,而這個獲取過程是排他的,也就是同一個時刻只能有一個線程獲取到由synchronized所保護對象的監視器。spa

synchronized的應用

synchronized實現同步的基礎是:Java中每一個對象均可以做爲鎖,具體表現爲如下三種形式:
1.對於普通同步方法,鎖是當前實例對象;
2.對於靜態同步方法,鎖是當前類的class對象;
3.對於同步方法塊,鎖是synchronized括號裏配置的對象操作系統

1)多個線程訪問的是多個對象.net

public class HasSelfPrivateNum {

    private int num = 0;

    public static void main(String[] args) {

        HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
        HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();

        ThreadTestA thread1 = new ThreadTestA(numRef1);
        thread1.start();

        ThreadTestB thread2 = new ThreadTestB(numRef2);
        thread2.start();
    }

    synchronized public void add(String username) {
        try {
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over!");

                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(username + " num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static public class ThreadTestA extends Thread {
        private HasSelfPrivateNum numRef;

        public ThreadTestA(HasSelfPrivateNum numRef) {
            this.numRef = numRef;
        }

        @Override
        public void run() {
            numRef.add("a");
        }
    }

    static public class ThreadTestB extends Thread {
        private HasSelfPrivateNum numRef;

        public ThreadTestB(HasSelfPrivateNum numRef) {
            this.numRef = numRef;
        }

        @Override
        public void run() {
            numRef.add("b");
        }
    }
}

圖片描述
兩個線程ThreadTestA和ThreadTestB分別訪問同一個類的不一樣實例的相同名稱的同步方法,可是效果確實異步執行,由於synchronized取得的鎖都是對象鎖,而不是把一段代碼或方法當作鎖。因此在上面的實例中,哪一個線程先執行帶synchronized關鍵字的方法,則哪一個線程就持有該方法所屬對象的鎖Lock,那麼其餘線程只能呈等待狀態。當前建立了兩個HasSelfPrivateNum類對象,因此就產生了兩個鎖。當ThreadTestA的引用執行到add方法run中的Thread.sleep(2000)語句時,ThreadB就會「伺機執行」。

2)多個線程訪問的是同一個對象

public static void main(String[] args) {

    HasSelfPrivateNum numRef = new HasSelfPrivateNum();

    ThreadTestA thread1 = new ThreadTestA(numRef);
    thread1.start();

    ThreadTestB thread2 = new ThreadTestB(numRef);
    thread2.start();
}

圖片描述
多個線程訪問的是同一個對象,哪一個線程先執行帶synchronized關鍵字的方法,則哪一個線程就持有該方法,那麼其餘線程只能呈等待狀態。若是多個線程訪問的是多個對象則不必定,由於多個對象會產生多個鎖。

3)髒讀
發生髒讀的狀況實在讀取實例變量時,此值已經被其餘線程更改過。

public class PublicVar {
    public String username = "A";
    public String password = "AA";

    synchronized public void setValue(String username, String password) {
        try {
            this.username = username;
            Thread.sleep(3000);
            this.password = password;

            System.out.println("setValue method thread name="
                    + Thread.currentThread().getName() + " username="
                    + username + " password=" + password);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //該方法前加上synchronized關鍵字就同步了
    public void getValue() {
        System.out.println("getValue method thread name="
                + Thread.currentThread().getName() + " username=" + username
                + " password=" + password);
    }

    public static void main(String[] args) {
        try {
            PublicVar publicVarRef = new PublicVar();
            ThreadC thread = new ThreadC(publicVarRef);
            thread.start();

            Thread.sleep(500);//打印結果受此值大小影響

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

    }

    static class ThreadC extends Thread {

        private PublicVar publicVar;

        public ThreadC(PublicVar publicVar) {
            this.publicVar = publicVar;
        }

        @Override
        public void run() {
            publicVar.setValue("B", "BB");
        }
    }
}

圖片描述

4)靜態同步synchronized方法與synchronized(class)代碼塊
synchronized關鍵字加到static靜態方法和synchronized(class)代碼塊上都是是給Class類上鎖,而synchronized關鍵字加到非static靜態方法上是給對象上鎖。

public class SynchronizedTest2 {

    public static void printA() {
        synchronized (SynchronizedTest2.class) {
            try {
                System.out.println(
                        "線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printA");
                Thread.sleep(3000);
                System.out.println(
                        "線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    synchronized public static void printB() {
        System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printB");
        System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printB");
    }

    synchronized public void printC() {
        System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printC");
        System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printC");
    }


    static class ThreadA extends Thread {
        private SynchronizedTest2 test;

        public ThreadA(SynchronizedTest2 test) {
            this.test = test;
        }

        @Override
        public void run() {
            test.printA();
        }
    }

    static class ThreadB extends Thread {
        private SynchronizedTest2 test;

        public ThreadB(SynchronizedTest2 test) {
            this.test = test;
        }

        @Override
        public void run() {
            test.printB();
        }
    }

    static class ThreadC extends Thread {
        private SynchronizedTest2 test;

        public ThreadC(SynchronizedTest2 test) {
            this.test = test;
        }

        @Override
        public void run() {
            test.printC();
        }
    }

    public static void main(String[] args) {
        SynchronizedTest2 test = new SynchronizedTest2();
        ThreadA a = new ThreadA(test);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(test);
        b.setName("B");
        b.start();

        ThreadC c = new ThreadC(test);
        c.setName("C");
        c.start();
    }
}

圖片描述
靜態同步synchronized方法與synchronized(class)代碼塊持有的鎖同樣,都是Class鎖,Class鎖對對象的全部實例起做用。synchronized關鍵字加到非static靜態方法上持有的是對象鎖。線程A,B和線程C持有的鎖不同,因此A和B運行同步,可是和C運行不一樣步。

參考文獻:
Java併發編程的藝術
https://blog.csdn.net/qq_3433...

相關文章
相關標籤/搜索