多線程知識梳理(2) synchronized 三部曲之基本使用

1、爲何要使用 synchronized

使用synchronized的緣由在於:它可以確保多個線程在同一時刻,只能有一個線程處於方法或者同步塊中,它保證了線程對變量訪問的可見性和排他性。bash

2、synchronized 原理

JDK 1.6以前,synchronized的實現是基於對象上的監視器,這也被稱爲重量鎖。默認狀況下,每個對象都有一個關聯的Monitor,而每一個Monitor包含了一個EntryCount計數器,它是synchronized實現可重入的關鍵。優化

JDK 1.6以後,對鎖進行了一系列優化的措施,經過引入自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減小鎖操做的開銷。this

這些優化措施最終的目的是減小鎖操做的開銷,然而它所改變的只是鎖的實現方式,可是加鎖和解鎖這一基本原則是沒有改變的。這篇文章主要是介紹synchronized的使用,所以,在後面的介紹中,咱們仍是按照比較容易理解的重量鎖的方式進行分析,在以後的文章中,咱們再來談一下優化後的實現策略。spa

2.1 進入同步方法或者代碼塊

當一個線程執行某個對象的同步方法或者代碼塊時,會先檢查這個對象所關聯的Monitor's EntryCount是否爲0線程

  • 若是EntryCount0,那麼該線程就會將Monitor’s EntryCount設置爲1,併成爲該Monitor的全部者,接着執行該方法或者代碼塊中的語句。
  • 若是EntryCount不爲0,這時會去檢查對象所關聯的Monitor的持有者是哪個線程:
  • 第一種狀況:持有該Monitor的線程就是當前正在嘗試獲取Monitor的線程,那麼將EntryCount的數值加1,繼續執行方法或者代碼塊中的語句。
  • 第二種狀況:持有該Monitor的是其它的線程,那麼該線程進入阻塞狀態,直到EntryCount的數值變爲0

2.2 退出同步方法或者代碼塊

當一個線程從同步方法或者代碼塊退出時,會將EntryCount1,若是EntryCount變爲0,那麼該線程會釋放它所持有的Monitor。以前那些阻塞在synchronized的線程會嘗試去獲取Monitor,成功獲取Monitor的線程能夠進入同步方法或者代碼塊。code

3、synchronized 使用

對於synchronized的使用,咱們有兩種分類方法:對象

  • 根據使用場景分類
  • 根據Monitor關聯的對象分類。

3.1 根據使用場景分類

不少介紹synchronized的文章,都是經過使用場景進行分類的,通常來講能夠分爲以下四種使用場景,而每種場景下根據Monitor所關聯的對象不一樣,又會衍生出另外的用法:同步

  • 靜態方法
//靜態方法,使用的是Class類鎖
    synchronized public static void staticMethod() {}
複製代碼
  • 靜態方法代碼塊
private static final byte[] mStaticLockByte = new byte[1];

    //靜態方法代碼塊1,使用的是Class類鎖
    public static void staticBlock1() {
        synchronized (SynchronizedObject.class) {}
    }

    //靜態方法代碼塊2,使用的是內部靜態變量鎖
    public static void staticBlock2() {
        synchronized (mStaticLockByte) {} 
    }
複製代碼
  • 普通方法
//普通方法,使用的是調用該方法的對象鎖
    synchronized public void method() {}
複製代碼
  • 普通方法代碼塊
private static final byte[] mStaticLockByte = new byte[1];
    private final byte[] mLockByte = new byte[1];

    //普通方法代碼塊1,使用的是Class類鎖
    public void block1() {
        synchronized (SynchronizedObject.class) {}
    }

    //普通方法代碼塊2,使用的是mLockByte的變量鎖
    public void block2() {
        synchronized (mLockByte) {} //變量須要聲明爲final
    }
    
    //普通方法代碼塊3,使用的是mStaticLockByte的變量鎖
    public void block3() {
        synchronized (mStaticLockByte) {} 
    }

    //普通方法代碼塊4,使用的是調用該方法的對象鎖
    public void block4() {
        synchronized (this) {}
    }
複製代碼

3.2 根據 Monitor 關聯的對象分類

根據使用場景進行分類,主要是爲了讓你們知道如何使用synchronized關鍵字,然而要真正地理解synchronized,就須要結合第二節談到的synchronized原理,其實3.1中談到的多種場景,都是和Monitor有關,那麼從和Monitor關聯的對象來看,咱們從新對3.1中的8種場景從新進行分類:it

  • Class對象:
  • 靜態方法
  • 靜態方法代碼塊1 - SynchronizedObject.class
  • 普通方法代碼塊1 - SynchronizedObject.class
  • 調用方法的對象
  • 普通方法
  • 普通方法代碼塊4 - this
  • 靜態對象
  • 靜態方法代碼塊2 - mStaticLockByte
  • 普通方法代碼塊3 - mStaticLockByte
  • 非靜態對象
  • 普通方法代碼塊1 - mLockByte

若是使用場景屬於上面的同一個分類當中,那麼纔有可能產生線程阻塞在synchronized關鍵字的狀況,舉一個例子,若是A線程經過靜態方法訪問(分類一)而且沒有從該方法退出:io

  • 這時B線程是經過一個對象的普通方法來訪問(分類二),那麼是不會阻塞的,這是由於調用該方法的對象所關聯的Monitor沒有被持有。
  • 若是B線程使用的是靜態方法代碼塊來訪問,而該靜態方法代碼塊使用的是SynchronizedObject.class來修飾(分類一),因爲這兩種使用場景是屬於同一個分類,那麼就會B線程就會進入阻塞狀態,這是由於SynchronizedObject類所關聯的Monitor已經被A線程持有了。

4、小結

從表面上來看,synchronized的使用能夠簡單地分爲同步方法和同步代碼塊,可是究竟在什麼狀況下會致使一個線程在synchronized上阻塞,則須要分析synchronized方法所嘗試獲取的Monitor的是否已經被其它線程持有了。

相關文章
相關標籤/搜索