Synchronized使用方法


Synchronized是我們常用來維持線程安全時使用的一個關鍵字,內部通過monitor(監視器鎖,由C++實現)來實現。而monitor本質又是依賴底層操作系統的mutex lock來實現。而操作系統實現線程之間的切換,需要從用戶態切換到核心態,這個的成本非常高,狀態之間的轉換需要相對較長的時間,這也就是為什么synchronized效率低的原因。這種依賴mutex lock所實現的鎖統稱為“重量級鎖”。jdk對Synchronized的優化,核心都是減少這種重量級鎖的使用。

 

monitor:由C++實現,可以與對象一起創建和銷毀,或者在線程嘗試獲取對象鎖時創建。

一個線程嘗試獲取對象鎖,會先令_owner指向該線程,同時_count自增1,這時候有其他線程嘗試獲取鎖時,會先存入_EntryList集合,並進入阻塞。當前線程執行完成后,令_count自減1,_owner=null,同時將其放到_WaitSet中去。

如果線程被調用了wait()等方式,釋放鎖時,也會令_owner=null,_count減1,同時將該線程放入_WaitSet集合,等待喚醒。

 

Synchronized可以使用在三個地方:

  1. 非靜態方法
  2. 靜態方法
  3. 代碼塊

首先我們先看一個普通的多線程方法

public class Main6  {

    public void method1() {
        System.out.println("method1 start");
        try {
            System.out.println("method1 exec");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("method1 completion");
    }

    public void method2() {
        System.out.println("method2 start");
        try {
            System.out.println("method2 exec");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("method2 completion");
    }

    public static void main(String[] args) {
        Main6 m1 = new Main6();
        Main6 m2 = new Main6();

        new Thread(new Runnable() {
            @Override
            public void run() {
                m1.method1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                m1.method2();
            }
        }).start();
    }
}

在這個方法執行的時候,method1()和method2()並行運行,但由於method2()的運行時間短,因此總是會先運行結束。

 

1.非靜態方法

public class Main6  {

    public synchronized void method1() {
        System.out.println("method1 start");
        try {
            System.out.println("method1 exec");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("method1 completion");
    }

    public synchronized void method2() {
        System.out.println("method2 start");
        try {
            System.out.println("method2 exec");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("method2 completion");
    }

    public static void main(String[] args) {
        Main6 m1 = new Main6();
        Main6 m2 = new Main6();

        new Thread(new Runnable() {
            @Override
            public void run() {
                m1.method1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                m1.method2();
            }
        }).start();
    }
}

在非靜態方法上添加synchronized關鍵字,當我們使用同一個對象分別在不同線程中調用該方法時候,會出現如下順序的結果:

method1 start
method1 exec
method1 completion
method2 start
method2 exec
method2 completion

這是由於synchronized會獲取當前調用對象的鎖,當線程1在執行m1.method1();時候獲取到了m1對象的鎖,線程2企圖執行m1.method2()方法時發現m1的鎖已經被其他線程獲取,因此會進入阻塞狀態,直到線程1對method1()方法執行完。

注意,由於調用線程1的start()方法並不是一定會立即開始執行線程1,它先會與main線程爭奪cpu等資源,因此有可能method2先執行,method1進入阻塞,直到method2執行完成后才會開始執行。

同時由於synchronized使用在非靜態方法上獲取的是當前對象的鎖,因此假如線程1和2分別使用的是兩個不同對象的mthod1方法和method2方法,則不會發生阻塞。

2.靜態方法

public class Main6  {

    public static synchronized void method1() {
        System.out.println("method1 start");
        try {
            System.out.println("method1 exec");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("method1 completion");
    }

    public static synchronized void method2() {
        System.out.println("method2 start");
        try {
            System.out.println("method2 exec");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("method2 completion");
    }

    public static void main(String[] args) {
        Main6 m1 = new Main6();
        Main6 m2 = new Main6();

        new Thread(new Runnable() {
            @Override
            public void run() {
                m1.method1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                m2.method2();
            }
        }).start();
    }
}

靜態方法與非靜態方法的不同處在於方法是屬於類,而不是屬於當前對象。

這樣即使我們調用的是不同對象的method1方法和method2方法,但是它們獲取的鎖是類對象的鎖(Class對象的鎖),因此,不論使用的是哪個對象的method1和method2,均會發生線程阻塞的情況。只有當一個線程中的靜態synchronized執行完成后才會執行另一個線程中的靜態synchronized方法。

3.代碼塊

public class Main6  {

    public void method1() {
        System.out.println("method1 start");
        synchronized(this) {
            try {
                System.out.println("method1 exec");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("method1 completion");
    }

    public void method2() {
        System.out.println("method2 start");
        synchronized (this) {
            try {
                System.out.println("method2 exec");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("method2 completion");
    }

    public static void main(String[] args) {
        Main6 m1 = new Main6();new Thread(new Runnable() {
            @Override
            public void run() {
                m1.method1();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                m1.method2();
            }
        }).start();
    }
}

輸出的結果可能如下:

method1 start
method1 exec
method2 start
method1 completion
method2 exec
method2 completion

當synchronized被使用在代碼塊上時,它所獲取的鎖是括號中接收的對象的鎖(如樣例代碼中的this指代的是當前調用它的對象),這樣,假設method1方法正在執行,並且進入代碼塊休眠3秒,這時method2獲取cpu,開始執行第一行代碼打印"method2 start",然后遇見代碼塊,嘗試獲取this對象的鎖,卻發現已經被method1獲取(sleet不釋放鎖),因此method2進入阻塞狀態,直到method1執行完畢后釋放鎖,method2獲取鎖開始執行代碼塊。

 

 在代碼塊中調用Synchronized(下圖),側重點在於第3步monitorenter,獲取monitor,並對當前_count加一。里面內容執行完成后,在第15步monitorexit,令_count減一,同時釋放monitor。

 

而當Synchronized被使用在方法上時,添加的是一個ACC_SYNCHRONIZED標志位,如果有該標志位,則判定當前方法使用了Synchronized

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM