synchronized關鍵字使用詳解

簡述

計算機單線程在執行任務時,是嚴格按照程序的代碼邏輯,按照順序執行的。所以單位時間內能執行的任務數量有限。爲了能在相同的時間內能執行更多的任務,就必須採用多線程的方式來執行(注意:多線程模式沒法減小單次任務的執行時間)。可是引入了多線程以後,又帶來了線程安全的問題。而爲了解決線程安全的問題,又引入了鎖的概念。java中經常使用的鎖有synchronizedlock兩種,本文咱們來分析synchronized的具體用法和使用注意事項。java

基本使用

同步代碼塊spring

/**
 * 同步代碼塊
 * @throws Exception
 */
public void synchronizedCode() {
    try {
        synchronized (this) {
            System.out.println(getCurrentTime() + ":I am synchronized Code");
            Thread.sleep(5000);//延時5秒,方便後面測試
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

做用代碼塊時,synchronized方法中的this,是指調用該方法的對象。須要主要的是,synchronized做用代碼塊時,只會鎖住這一小塊代碼。代碼塊的上下部分的其餘代碼在全部的線程仍然是能同時訪問的。同時須要注意的是每一個對象有用不一樣的鎖。即不會阻塞不一樣對象的調用。安全

同步方法多線程

/**
  * 同步方法
  */
public synchronized void synchronizedMethod() {
    try {
        System.out.println(getCurrentTime() + ":I am synchronized method");
        Thread.sleep(5000);//延時5秒,方便後面測試
    } catch (Exception e) {
        e.printStackTrace();
    }
}

synchronized做用在方法上,實際上是缺省了this關鍵字,其實是synchronized(this)。this是指調用該方法的對象。此鎖也不會阻塞不一樣對象之間的調用。ide

同步靜態方法測試

/**
* 同步靜態方法
*/
public synchronized static void synchronizedStaticMethod() {
    try {
        System.out.println(getCurrentTime() + ":I am synchronized static method");
        Thread.sleep(5000);//延時5秒,方便後面測試
    } catch (Exception e) {
        e.printStackTrace();
    }
}

使用方式和做用普通方式相同,惟一須要注意的地方是此鎖全部對象共用,即不一樣對象之間會阻塞調用。this

測試準備

簡單說明一下:有一個線程池,在執行多任務時使用。每一個同步方法或者代碼塊中都有一個休眠5秒的動做,利用打印時間加休眠來看線程之間是否有阻塞效果。而後有一個1秒打印一次時間的方法。線程

public class Synchronized {
    //打印時間時格式化
    public static final String timeFormat = "HH:mm:ss";
    //執行多任務的線程池
    public static final ExecutorService executor = Executors.newFixedThreadPool(4);

    /**
     * 同步代碼塊
     * @throws Exception
     */
    public void synchronizedCode() {
        try {
            synchronized (this) {
                System.out.println(getCurrentTime() + ":I am synchronized Code");
                Thread.sleep(5000);//延時5秒,方便後面測試
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 同步方法
     */
    public synchronized void synchronizedMethod() {
        try {
            System.out.println(getCurrentTime() + ":I am synchronized method");
            Thread.sleep(5000);//延時5秒,方便後面測試
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 同步靜態方法
     */
    public synchronized static void synchronizedStaticMethod() {
        try {
            System.out.println(getCurrentTime() + ":I am synchronized static method");
            Thread.sleep(5000);//延時5秒,方便後面測試
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 循環打印時間
     */
    public static void printNumber() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        printOnceASecond();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    /**
     * 一秒打印一次時間
     *
     * @throws Exception
     */
    public static void printOnceASecond() throws Exception {
        System.out.println(getCurrentTime());
        Thread.sleep(1000);
    }

    /**
     * 獲取當前時間
     *
     * @return
     */
    public static String getCurrentTime() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(timeFormat));
    }
}

OK,接下來咱們就來測試下鎖的互斥性以及使用注意事項(都是多線程的狀況下)。code

開始測試

同一個對象同步代碼塊orm

public static void main(String[] args) throws Exception {
    printNumber();//控制檯循環打印時間
    Synchronized es = new Synchronized();
    executor.execute(() -> es.synchronizedCode());
    executor.execute(() -> es.synchronizedCode());
}

execute

20:34:41:I am synchronized Code
20:34:41
20:34:42
20:34:43
20:34:44
20:34:45
20:34:46:I am synchronized Code

同步代碼塊中休眠5秒,致使另一個線程阻塞5秒後再執行。說明代同步碼塊會阻塞同一個對象的不一樣線程之間的調用(同步方法和同步靜態方法也會阻塞同一個對象的不一樣線程之間的調用,此處省略測試代碼)

不一樣對象同步代碼塊

public static void main(String[] args) throws Exception {
    printNumber();//控制檯循環打印時間
    Synchronized es = new Synchronized();
    Synchronized es1 = new Synchronized();
    executor.execute(() -> es.synchronizedCode());
    executor.execute(() -> es1.synchronizedCode());
}

execute

20:44:34:I am synchronized Code
20:44:34:I am synchronized Code

由結果能夠看出,不一樣對象之間代碼塊鎖互不影響(多線程也同樣)。緣由是由於代碼塊中synchronized (this)

鎖的是當前調用對象,不一樣對象之間不是同一把鎖,所以互不影響(同步方法原理也是如此,省略測試代碼)。

同一對象同步代碼塊和方法

public static void main(String[] args) throws Exception {
    printNumber();//控制檯循環打印時間
    Synchronized es = new Synchronized();
    executor.execute(() -> es.synchronizedCode());
    executor.execute(() -> es.synchronizedMethod());
}

execute

20:51:27:I am synchronized method
20:51:27
20:51:28
20:51:29
20:51:30
20:51:31
20:51:32:I am synchronized Code

由於同步代碼塊和同步方法,都是鎖當前調用對象,所以執行後打印上述結果應該在乎料之中。基於這樣的特性,實際開發在使用spring的時候就須要注意了,咱們的bean交給spring容器管理以後,默認都是單例的。那麼這個時候使用synchronized關鍵字就須要注意了(推薦使用同步代碼塊,同步的代碼塊中傳入外部定義的一個變量)。

不一樣對象靜態同步方法

public static void main(String[] args) throws Exception {
    printNumber();//控制檯循環打印時間
    Synchronized es = new Synchronized();
    Synchronized es1 = new Synchronized();
    executor.execute(() -> es.synchronizedStaticMethod());
    executor.execute(() -> es1.synchronizedStaticMethod());
}

execute

21:05:39:I am synchronized static method
21:05:40
21:05:41
21:05:42
21:05:43
21:05:44:I am synchronized static method

由上述結果能夠看出來,靜態同步方法會阻塞全部的對象。緣由是全部的靜態同步方法都是佔用的同一把鎖。

相同對象同步方法和靜態同步方法

public static void main(String[] args) throws Exception {
    printNumber();//控制檯循環打印時間
    Synchronized es = new Synchronized();
    executor.execute(() -> es.synchronizedMethod());
    executor.execute(() -> es.synchronizedStaticMethod());
}

execute

21:11:03:I am synchronized static method
21:11:03:I am synchronized method

由此結果能夠看出,同步方法和靜態同步方法之間不會形成阻塞的現象。由於他們鎖的對象不同。同步方法佔用的鎖是調用對象,靜態同步方法鎖的是編譯後的class對象(類鎖)。

總結

  • 同一個對象,同步方法、同步代碼塊之間互斥,同時和本身也互斥。靜態同步方法只和本身互斥
  • 不一樣對象之間,同步方法、同步代碼塊不會互斥。靜態同步方法會互斥
  • synchronized在佔用鎖時,必須精確到某一個具體的對象
相關文章
相關標籤/搜索