「Java多線程」內(nèi)置鎖(Synchronized)的前世今生

<article class="syl-article-base tt-article-content syl-page-article syl-device-pc" style="box-sizing: border-box; display: block; padding: 0px; text-align: justify; overflow-wrap: break-word; word-break: break-word; overflow: hidden; color: rgb(34, 34, 34); font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", "Helvetica Neue", Arial, sans-serif; line-height: 1.667; font-size: 18px; margin-bottom: 20px;">

什么互斥和同步

  • 互斥是指某一資源同一時(shí)間只允許一個(gè)訪問(wèn)者對(duì)其進(jìn)行訪問(wèn),具有唯一性和排它性。但互斥無(wú)法控制對(duì)資源的訪問(wèn)順序
  • 同步是指在互斥的基礎(chǔ)上實(shí)現(xiàn)對(duì)資源的有序訪問(wèn),即:也是不可以同時(shí)訪問(wèn),并且還需要按照某種順序來(lái)運(yùn)行。

什么是互斥量

互斥量mutex

  • 是Linux提供一把 互斥鎖 mutex(也稱之為 互斥量 )
  • 用于對(duì)共享資源加鎖,保證一時(shí)間只允許一個(gè)線程對(duì)其進(jìn)行訪問(wèn)

線程安全三大特性

【Java多線程】重溫并發(fā)BUG的源頭之可見(jiàn)性、原子性、有序性

二.為什么要用鎖?

  • 鎖可以解決并發(fā)執(zhí)行任務(wù)執(zhí)行過(guò)程中對(duì) 共享數(shù)據(jù)順序訪問(wèn)、修改的場(chǎng)景 。比如對(duì)同時(shí)對(duì)一個(gè)賬戶進(jìn)行 扣款 或者 轉(zhuǎn)賬 。

三.什么是內(nèi)置鎖

Java內(nèi)置鎖不需要顯式的獲取鎖和釋放鎖,由JVM內(nèi)部來(lái)實(shí)現(xiàn)鎖的獲取與釋放。而且任何一個(gè)對(duì)象都能作為一把內(nèi)置鎖。在 JDK1.4及之前就是使用 內(nèi)置鎖Synchronized來(lái)進(jìn)行線程同步控制的

上文說(shuō),任何一個(gè)對(duì)象都能作為一把內(nèi)置鎖”,意味著synchronized關(guān)鍵字出現(xiàn)的地方,都有一個(gè)對(duì)象與之關(guān)聯(lián) ,具體表現(xiàn)為:

  • 當(dāng)synchronized作用于 普通方法 時(shí),鎖對(duì)象是 this ;
  • 當(dāng)synchronized作用于 靜態(tài)方法 時(shí),鎖對(duì)象是 當(dāng)前類的Class對(duì)象 ;
  • 當(dāng)synchronized作用于 代碼塊 時(shí),鎖對(duì)象是 synchronized(obj)中的這個(gè)obj

原理

  • 由 JVM 虛擬機(jī)內(nèi)部實(shí)現(xiàn),是基于 monitor 機(jī)制 ,每個(gè)對(duì)象都存在著一個(gè) 監(jiān)視器(monitor)實(shí)與之關(guān)聯(lián),monitor的本質(zhì)是依賴于底層操作系統(tǒng)的實(shí)現(xiàn),稱為內(nèi)部鎖或者M(jìn)onitor鎖。Monitor是線程私有的數(shù)據(jù)結(jié)構(gòu),每一個(gè)線程都有一個(gè) 可用monitor record列表 ,同時(shí)還有一個(gè)全局的可用列表。 每一個(gè)被鎖住的對(duì)象都會(huì)和一個(gè)monitor關(guān)聯(lián) ,同時(shí)monitor中有一個(gè) Owner字段 存放擁有該鎖的線程的唯一標(biāo)識(shí),表示該鎖被這個(gè)線程占用。

優(yōu)缺點(diǎn):

  • 優(yōu)點(diǎn): 即內(nèi)置鎖的特性,不需要使用者顯示的獲取鎖和釋放鎖
  • 缺點(diǎn): 線程拿不到鎖就會(huì)一直等待 ,除了獲取鎖沒(méi)有其他辦法能夠讓其結(jié)束等待
  • 詳情情況我的這篇文章 【Java多線程】了解線程的鎖池和等待池概念 已經(jīng)寫(xiě)清楚了,偷個(gè)懶,就不重復(fù)造輪子了。

四.synchronized使用

當(dāng)多個(gè)線程同時(shí)運(yùn)行時(shí),線程的調(diào)度由操作系統(tǒng)決定,程序本身無(wú)法決定。因此,任何一個(gè)線程都有可能在任何指令處被操作系統(tǒng)暫停,然后在某個(gè)時(shí)間段后繼續(xù)執(zhí)行。這個(gè)時(shí)候,有個(gè)單線程場(chǎng)景下不存在的問(wèn)題就來(lái)了: 如果 多個(gè)線程時(shí)讀寫(xiě)共享變量 ,會(huì)出現(xiàn)數(shù)據(jù)不一致的問(wèn)題。

1.線程安全問(wèn)題產(chǎn)生

public class SyncTest1 {

    public static void main(String[] args) throws Exception {

        AddThread add = new AddThread();
        DecThread dec = new DecThread();
        add.start();
        dec.start();
        add.join();
        dec.join();
        System.out.println(Counter.count);
    }
}

//計(jì)數(shù)器
class Counter {

    public static int count = 0;
}
//自增線程
class AddThread extends Thread {

    public void run() {

        for (int i=0; i<10000; i++) {
  Counter.count += 1; }
    }
}
//自減線程
class DecThread extends Thread {

    public void run() {

        for (int i=0; i<10000; i++) {
  Counter.count -= 1; }
    }
}

上面的代碼 兩個(gè)線程同時(shí)對(duì)一個(gè)int變量進(jìn)行操作 ,一個(gè)加10000次,一個(gè)減10000次,最后結(jié)果應(yīng)該是0,但是, 每次運(yùn)行,結(jié)果實(shí)際上都是不一樣的。

連續(xù)執(zhí)行三次結(jié)果

image.png
image.png
image.png

這是因?yàn)閷?duì)變量進(jìn)行讀取和寫(xiě)入時(shí),結(jié)果要正確,必須保證是 原子操作 。原子操作是指不能被中斷的一個(gè)或一系列操作。

實(shí)際上執(zhí)行 n = n + 1 并不是一個(gè)原子操作,它的執(zhí)行過(guò)程如下:

  1. 從主存中讀取變量x副本到工作內(nèi)存
  2. 給x加1
  3. 將x加1后的值寫(xiě)回主存

我們假設(shè) n 的值是 100 ,如果 兩個(gè)線程同時(shí)執(zhí)行n = n + 1 ,得到的結(jié)果很可能不是 102,而是101 ,

原因在于: 多個(gè)線程執(zhí)行時(shí),CPU對(duì)線程的調(diào)度是隨機(jī)的,我們不知道當(dāng)前程序被執(zhí)行到哪步就切換到了下一個(gè)線程

  • 如果 線程1 在從主內(nèi)存將n=100的值同步到工作內(nèi)存時(shí),此時(shí)cpu切換到 線程2 , 線程2 也將n=100的值同步到工作內(nèi)存
  • 線程1 n+=1 = 101,然后同步到主內(nèi)存此時(shí)主內(nèi)存為101
  • 線程2 n-=1 = 99,然后同步到主內(nèi)存此時(shí)主內(nèi)存為99
  • 顯然由于執(zhí)行順序的不同n最終的結(jié)果可能為101也可能為99

這說(shuō)明多線程場(chǎng)景下,要保證邏輯正確, 即某一個(gè)線程對(duì)共享變量進(jìn)行讀寫(xiě)時(shí),其他線程必須等待

2.初識(shí)Synchronized

  • 通過(guò)加鎖和解鎖的操作,就能保證在一個(gè)線程執(zhí)行期間,不會(huì)有其他線程會(huì)進(jìn)入此代碼塊。
  • 即使在執(zhí)行期線程被操作系統(tǒng)中斷執(zhí)行,其他線程也會(huì)因?yàn)闊o(wú)法獲得鎖導(dǎo)致無(wú)法進(jìn)入此代碼塊。只有執(zhí)行線程將鎖釋放后,其他線程才有機(jī)會(huì)獲得鎖并執(zhí)行。這種 加鎖和解鎖之間的代碼塊 我們稱之為 臨界區(qū)(Critical Section) , 任何時(shí)候臨界區(qū)最多只有一個(gè)線程能執(zhí)行。
  • Java使用 synchronized關(guān)鍵字 對(duì)一個(gè)對(duì)象進(jìn)行加鎖、解鎖,以保證操作的原子性。

如何使用Synchronized

synchronized(lockObject) { }.

  • 在使用synchronized的時(shí)候, 不必?fù)?dān)心拋出異常 。 因?yàn)闊o(wú)論是否有異常,都會(huì)在synchronized結(jié)束處正確釋放鎖:
public void add(int m) {

    synchronized (obj) {

        if (m < 0) {

            throw new RuntimeException();
        }
        this.value += m;
    } // 無(wú)論有無(wú)異常,都會(huì)在此釋放鎖
}

使用synchronized優(yōu)化SyncTest1案例中線程不安全問(wèn)題

public class SyncTest2 {

    public static void main(String[] args) throws Exception {

        AddThread add = new AddThread();
        DecThread dec = new DecThread();
        add.start();
        dec.start();
        add.join();
        dec.join();
        System.out.println(Counter.count);
    }
}
//計(jì)數(shù)器
class Counter {

    public static final Object lock = new Object();
    public static int count = 0;
}
//自增線程
class AddThread extends Thread {

    public void run() {

        for (int i=0; i<10000; i++) {
     synchronized(Counter.lock) {
  Counter.count += 1;} }
    }
}
//自減線程
class DecThread extends Thread {

    public void run() {

        for (int i=0; i<10000; i++) {
   synchronized(Counter.lock){
  Counter.count -= 1;} }
    }
}

執(zhí)行結(jié)果

image.png

代碼

synchronized(Counter.lock) {
 //獲取鎖

  }//釋放鎖
  • 它表示用 Counter.lock實(shí)例 作為鎖,2個(gè)線程在執(zhí)行各自的 synchronized(Counter.lock) { ... } 代碼塊時(shí), 必須先獲得鎖,才能進(jìn)入代碼塊進(jìn)行。 執(zhí)行結(jié)束后,在synchronized語(yǔ)句塊結(jié)束會(huì)自動(dòng)釋放鎖。 這將會(huì)導(dǎo)致對(duì)Counter.count變量進(jìn)行讀寫(xiě)就 不能同時(shí)進(jìn)行 。無(wú)論運(yùn)行多少次,最終結(jié)果都是0。

synchronized解決了多線程同步訪問(wèn)共享變量的有序性問(wèn)題。但它的缺點(diǎn)是帶來(lái)了性能下降。因?yàn)閟ynchronized代碼塊無(wú)法并發(fā)執(zhí)行。此外 加鎖和解鎖需要消耗一定的時(shí)間,所以, synchronized會(huì)降低程序的執(zhí)行效率。

3.錯(cuò)誤使用Synchronized的案例

3.1.案例1

public class Main {

    public static void main(String[] args) throws Exception {

        AddThread add = new AddThread();
        DecThread dec = new DecThread();
        add.start();
        dec.start();
        add.join();
        dec.join();
        System.out.println(Counter.count);
    }
}

class Counter {

    public static final Object lock1 = new Object();
    public static final Object lock2 = new Object();
    public static int count = 0;
}

class AddThread extends Thread {

    public void run() {

        for (int i=0; i<10000; i++) {

            synchronized(Counter.lock1) {

                Counter.count += 1;
            }
        }
    }
}

class DecThread extends Thread {

    public void run() {

        for (int i=0; i<10000; i++) {

            synchronized(Counter.lock2) {

                Counter.count -= 1;
            }
        }
    }
}

執(zhí)行結(jié)果

image.png

結(jié)果并不是0,這是因?yàn)?2個(gè)線程各自的synchronized鎖住的不是同一個(gè)對(duì)象! 這使得2個(gè)線程各自都可以同時(shí)獲得鎖: 因?yàn)镴VM只保證同一個(gè)鎖在任意時(shí)刻只能被一個(gè)線程獲取,但兩個(gè)不同的鎖在同一時(shí)刻可以被2個(gè)線程分別獲取 。 使用synchronized的時(shí)候,獲取到的是哪個(gè)鎖非常重要。鎖對(duì)象如果不對(duì),代碼邏輯就不對(duì)。

3.2.案例2

public class SyncTest3 {

    public static void main(String[] args) throws Exception {

        Thread [] ts = new Thread[] {
  new AddStudentThread(), new DecStudentThread(), new AddTeacherThread(), new DecTeacherThread() };
        for (Thread t : ts) {

            t.start();
        }
        for (Thread t : ts) {

            t.join();
        }
        System.out.println(Counter.studentCount);
        System.out.println(Counter.teacherCount);
    }
}

class Counter {

    public static final Object lock = new Object();
    public static int studentCount = 0;
    public static int teacherCount = 0;
}

class AddStudentThread extends Thread {

    public void run() {

        for (int i=0; i<10000; i++) {

            synchronized(Counter.lock) {

                Counter.studentCount += 1;
            }
        }
    }
}

class DecStudentThread extends Thread {

    public void run() {

        for (int i=0; i<10000; i++) {

            synchronized(Counter.lock) {

                Counter.studentCount -= 1;
            }
        }
    }
}

class AddTeacherThread extends Thread {

    public void run() {

        for (int i=0; i<10000; i++) {

            synchronized(Counter.lock) {

                Counter.teacherCount += 1;
            }
        }
    }
}

class DecTeacherThread extends Thread {

    public void run() {

        for (int i=0; i<10000; i++) {

            synchronized(Counter.lock) {

                Counter.teacherCount -= 1;
            }
        }
    }
}

執(zhí)行結(jié)果

image.png
  • 上面 4個(gè)線程 對(duì) 兩個(gè)共享變量 分別進(jìn)行讀寫(xiě)操作,但是使用的鎖都是 Counter.lock對(duì)象 ,這就造成了 原本可以并發(fā)執(zhí)行的Counter.studentCount += 1和Counter.teacherCount += 1無(wú)法并發(fā)執(zhí)行了 , 執(zhí)行效率大大降低 。
  • 實(shí)際上,需要同步的線程可以分成2組: AddStudentThread和DecStudentThread , AddTeacherThread和DecTeacherThread ,組之間不存在競(jìng)爭(zhēng)關(guān)系,因此,應(yīng)該使用2個(gè)不同的鎖
public class SyncMultiTest3 {

    public static void main(String[] args) throws Exception {

        //創(chuàng)建線程
        Thread[] ts = new Thread[]{
 new AddStudentThread(), new DecStudentThread(), new AddTeacherThread(), new DecTeacherThread()};
        //啟動(dòng)線程
        for (Thread t : ts) {

            t.start();
        }
        //優(yōu)先子線程先執(zhí)行
        for (Thread t : ts) {

            t.join();
        }
        //最后打印執(zhí)行結(jié)果
        System.out.println(Counter.studentCount);
        System.out.println(Counter.teacherCount);
    }
}

//計(jì)數(shù)器
class Counter {

    public static final Object lockTeacher = new Object();//學(xué)生線程鎖對(duì)象
    public static final Object lockStudent = new Object();//老師線程鎖對(duì)象
    public static int studentCount = 0;
    public static int teacherCount = 0;
}

//增加學(xué)生數(shù)量線程
class AddStudentThread extends Thread {

    public void run() {

        for (int i = 0; i < 10000; i++) {

            synchronized (Counter.lockStudent) {

                Counter.studentCount += 1;
            }
        }
    }
}

//減少學(xué)生數(shù)量線程
class DecStudentThread extends Thread {

    public void run() {

        for (int i = 0; i < 10000; i++) {

            synchronized (Counter.lockStudent) {

                Counter.studentCount -= 1;
            }
        }
    }
}

//增加老師數(shù)量線程
class AddTeacherThread extends Thread {

    public void run() {

        for (int i = 0; i < 10000; i++) {

            synchronized (Counter.lockTeacher) {

                Counter.teacherCount += 1;
            }
        }
    }
}

//減少老師數(shù)量線程
class DecTeacherThread extends Thread {

    public void run() {

        for (int i = 0; i < 10000; i++) {

            synchronized (Counter.lockTeacher) {

                Counter.teacherCount -= 1;
            }
        }
    }
}

執(zhí)行結(jié)果

image.png

3.3.案例3

JVM規(guī)范定義了幾種原子操作:

  • 基本類型(long和double除外)賦值,例如:int n = m;

  • 引用類型賦值,例如:List list = anotherList。

  • long和double是64位數(shù)據(jù),JVM沒(méi)有明確規(guī)定64位賦值操作是不是一個(gè)原子操作,不過(guò)在x64平臺(tái)的JVM是把long和double的賦值作為原子操作實(shí)現(xiàn)的。

  1. 單條原子操作的語(yǔ)句不需要同步。例如:
public void set(int m) {

    synchronized(lock) {

        this.value = m;
    }
}

就不需要同步

//引用類型賦值
public void set(String s) {

    this.value = s;
}
  1. 如果是多行賦值語(yǔ)句,就必須保證是同步操作
class Pair {

    int first;
    int last;
    public void set(int first, int last) {

        synchronized(this) {

            this.first = first;
            this.last = last;
        }
    }
}

有些時(shí)候,通過(guò)一些巧妙的轉(zhuǎn)換,可以把非原子操作變?yōu)樵硬僮?。例如,上述代碼如果改造成:

class Pair {

    int[] pair;
    public void set(int first, int last) {

        int[] ps = new int[] {
  first, last };
        this.pair = ps;
    }
}

就不再需要同步,因?yàn)?this.pair = ps 是 引用賦值的原子操作 。而語(yǔ)句: int[] ps = new int[] { first, last }; ,這里的 ps是方法內(nèi)部定義的局部變量 , 每個(gè)線程都會(huì)有各自的局部變量,互不影響,并且互不可見(jiàn),并不需要同步。

3.4.小結(jié)

  1. 多線程 同時(shí)讀寫(xiě)共享變量時(shí) ,會(huì)造成邏輯錯(cuò)誤,因此需要通過(guò) synchronized 同步;
  2. 同步的本質(zhì)就是給指定對(duì)象加鎖 ,加鎖后才能繼續(xù)執(zhí)行后續(xù)代碼
  3. 注意 加鎖對(duì)象必須是同一個(gè)實(shí)例 ;
  4. JVM定義的單個(gè)原子操作不需要同步

五.Jvm對(duì)synchronized的優(yōu)化

在 JDK1.6 之前, syncronized 是一把 重量級(jí)鎖,在 JDK 1.6之后為了 減少獲得鎖和釋放鎖帶來(lái)的性能消耗,會(huì)有一個(gè) 鎖升級(jí)的過(guò)程,給Synchronized加入了 "偏向鎖、自旋鎖、輕量級(jí)鎖"的特性,這些優(yōu)化使得Synchronized的性能在某些場(chǎng)景下與ReentrantLock的性能持平

  1. syncronized一共有 4 種鎖狀態(tài),級(jí)別從低到高依次是: 無(wú)鎖->偏向鎖->輕量級(jí)鎖->重量級(jí)鎖,這幾個(gè)狀態(tài)會(huì) 隨著競(jìng)爭(zhēng)情況逐漸升級(jí)。
  2. 鎖可以升級(jí)但不能降級(jí) ,意味著偏向鎖升級(jí)成輕量級(jí)鎖后不能降級(jí)成偏向鎖。這種鎖升級(jí)卻不能降級(jí)的策略, 目的是為了提高獲得鎖和釋放鎖的效率。

1.Java對(duì)象內(nèi)存結(jié)構(gòu)

對(duì)象在堆內(nèi)存中存儲(chǔ)的布局分為3塊

image.png

如上圖所示, 以Hotspot虛擬機(jī)為例, 在實(shí)例化一個(gè)對(duì)象后,在 Java內(nèi)存中的布局 可分為 3 塊:

1.對(duì)象頭包括2部分(ObjectHeader):

  • 對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)( MarkWord 標(biāo)記字段 )存儲(chǔ)對(duì)象hashCode、對(duì)象GC分代年齡、鎖類型標(biāo)記 、偏向鎖線程 ID 、 CAS 鎖指向線程 LockRecord 的指針等, synconized 鎖的機(jī)制與這個(gè)部分( MarkWord )密切相關(guān) ,用 MarkWord 中 最低的三位代表鎖的狀態(tài) ,其中一位是偏向鎖位,另外兩位是普通鎖位。在運(yùn)行期間, Mark Word 里面存儲(chǔ)的數(shù)據(jù)會(huì)隨著 鎖標(biāo)志位 的變化而變化。 MarkWord 可能變?yōu)榇鎯?chǔ)以下 5 種數(shù)據(jù),如下圖所示可以看到當(dāng)對(duì)象狀態(tài)為 偏向鎖 時(shí),Mark Word存儲(chǔ)的是偏向的 線程ID ;當(dāng)對(duì)象狀態(tài)為 輕量級(jí)鎖 時(shí),Mark Word存儲(chǔ)的是 指向線程棧中Lock Record(鎖記錄)的指針當(dāng)對(duì)象狀態(tài)為 重量級(jí)鎖 時(shí),Mark Word為 指向堆中的monitor對(duì)象的指針
  • 對(duì)象類型指針( ClassPointer )對(duì)象指向它的類元數(shù)據(jù)的指針 、 JVM 就是通過(guò)它來(lái)確定是哪個(gè) Class 的實(shí)例。

2.實(shí)例數(shù)據(jù)區(qū)域(InstanceData)

  • 此處存儲(chǔ)的是對(duì)象真正有效的信息 ,比如對(duì)象中所有變量的內(nèi)容,,其大小由各個(gè)變量的大小決定,比如:byte和boolean是1個(gè)字節(jié),short和char是2個(gè)字節(jié),int和float是4個(gè)字節(jié),long和double是8個(gè)字節(jié),reference是4個(gè)字節(jié)

3.對(duì)齊填充區(qū)域(Padding)

  • JVM 的實(shí)現(xiàn) HostSpot 規(guī)定 對(duì)象的起始地址必須是 8 字節(jié)的整數(shù)倍 ?,F(xiàn)在 64 位的 OS 往外讀取數(shù)據(jù)的時(shí)候一次性讀取 64bit 整數(shù)倍 的數(shù)據(jù),也就是 8 個(gè)字節(jié) ,所以 HotSpot 為了高效讀取對(duì)象,就做了 "對(duì)齊" , 如果一個(gè)對(duì)象實(shí)際占的內(nèi)存大小不是 8byte 的整數(shù)倍時(shí),就"補(bǔ)位"到 8byte 的整數(shù)倍。所以對(duì)齊填充區(qū)域的大小不是固定的。

2.JDK1.6中JVM對(duì)Synchronized的優(yōu)化

鎖消除和鎖粗化,適應(yīng)性自旋是虛擬機(jī)對(duì)低效的鎖操作而進(jìn)行的一個(gè)優(yōu)化。

2.1.鎖消除(Lock Elimination)

鎖削除是指JVM編譯器在運(yùn)行時(shí),對(duì)一些代碼上要求同步,但是被檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)的鎖進(jìn)行消除。

  • 簡(jiǎn)單來(lái)說(shuō),在編譯期間,JVM會(huì)清除一些使用了同步,但同步塊中沒(méi)有涉及共享數(shù)據(jù)的鎖,從而減少多余的同步。

如:使用StringBuffer的append方法,因?yàn)閍ppend方法需要判斷對(duì)象是否被占用,而如果代碼不存在鎖的競(jìng)爭(zhēng),那么這部分的性能消耗是無(wú)意義的。于是虛擬機(jī)在即時(shí)編譯的時(shí)候就會(huì)將上面代碼進(jìn)行優(yōu)化,也就是鎖消除。

那么虛擬機(jī)如何判斷不存在同步情況呢?通過(guò) 逃逸分析 ??梢?jiàn)下面?zhèn)未a

public static String createStringBuffer(String str1, String str2) {

        StringBuffer sb= new StringBuffer();
        sb.append(str1);// append方法是同步操作
        sb.append(str2);
        return sBuf.toString();// toString方法是同步操作
}
  • 可以看到 sb對(duì)象 使用的范圍僅僅只在方法棧 createStringBuffer 中,因?yàn)?return 回去的對(duì)象是一個(gè) 新的String對(duì)象 。
  • 也就是說(shuō) sb對(duì)象 是不會(huì) 逃逸 出去從而 被其他線程訪問(wèn)到 ,那就可以把它們當(dāng)作 棧 上的數(shù)據(jù)對(duì)待,認(rèn)為它們是 線程私有 ,同步鎖無(wú)需進(jìn)行。

2.2.鎖粗化(Lock Coarsening)

若有一系列操作,反復(fù)地對(duì)同一把鎖進(jìn)行加鎖和解鎖操作,編譯器會(huì)擴(kuò)大這部分代碼的同步塊的邊界,從而只使用一次上鎖和解鎖操作。。

public static StringBuffer createStringBuffer(String str1, String str2) {

    StringBuffer sBuf = new StringBuffer();
    sBuf.append(str1);// append方法是同步操作
    sBuf.append(str2);// append方法是同步操作
    sBuf.append("abc");// append方法是同步操作
    return sBuf;
}
  • 當(dāng) 頻繁的對(duì)sBuf進(jìn)行加鎖、解鎖 ,會(huì)造成性能上的損失。 如果虛擬機(jī)探測(cè)到有一系列連續(xù)操作都是對(duì)同一對(duì)象加鎖,將會(huì)把加鎖同步的范圍擴(kuò)展到整個(gè)操作的最外部,也就是在第一個(gè)和最后一個(gè)append操作之后
  • 用下面?zhèn)未a對(duì)鎖粗化進(jìn)行說(shuō)明
for(int i=0;i<100000;i++){

    synchronized(this){

        do();  
    }
}  

//在鎖粗化之后運(yùn)行邏輯如下列代碼
synchronized(this){

    for(int i=0;i<100000;i++){

        do();
    }    
}

2.3.適應(yīng)性自旋鎖(Adaptive Spinning)

背景:在許多場(chǎng)景中,同步資源的鎖定時(shí)間很短,為了這一小段時(shí)間去阻塞或喚醒一個(gè)線程的時(shí)間可能比用戶代碼執(zhí)行的時(shí)間還要長(zhǎng)。為了讓當(dāng)前線程“稍等一下”,我們可以讓線程進(jìn)行自旋,如果在自旋過(guò)程中占用同步資源的線程已經(jīng)釋放了鎖,那么當(dāng)前線程就可以不進(jìn)入阻塞而直接獲取同步資源,從而避免切換線程的開(kāi)銷。

自旋鎖(spinlock):即當(dāng)一個(gè)線程在獲取鎖的時(shí)候,如果鎖已經(jīng)被其它線程獲取,不是立即阻塞線程。那么該線程將 循環(huán)等待,然后 不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖才會(huì)退出循環(huán)。嘗試獲取鎖的線程不會(huì)立即 阻塞(放棄CPU時(shí)間片),采用 循環(huán)的方式嘗試獲取鎖!

  • 優(yōu)點(diǎn) : 不會(huì)使線程進(jìn)入阻塞狀態(tài)(放棄CPU時(shí)間片),通過(guò)占用CPU時(shí)間來(lái)避免線程切換帶來(lái)的開(kāi)銷 ,避免了線程在嘗試獲得鎖失敗后,在 “掛起-再次嘗試” 之間,不斷上下文切換造成的資源浪費(fèi)。
  • 缺點(diǎn) : 自旋等待雖然避免了線程切換的開(kāi)銷,但如果自旋鎖獲取鎖的時(shí)間太長(zhǎng),會(huì)造成后面的線程 CPU資源耗盡 ,因此自旋鎖只適用于 鎖的占用時(shí)間較短 的場(chǎng)景(自旋鎖是不公平的)。如果持有鎖的線程不能很快釋放鎖,線程CPU的占用時(shí)間(自旋過(guò)程)會(huì)過(guò)長(zhǎng),反而使得效率變低,性能下降。因此 虛擬機(jī)限制了自旋次數(shù)(默認(rèn)是10次,JDK1.6中通過(guò)-XX:+UseSpinning開(kāi)啟,可以使用-XX:PreBlockSpin來(lái)更改,JDK1.7后,去掉此參數(shù),由JVM控制),如果自旋超過(guò)了限定次數(shù),虛擬機(jī)會(huì)將線程掛起,讓出cpu資源。
image.png

什么是自適應(yīng)自旋鎖:即: 自旋的次數(shù)不再固定 , 由前一次在 同一個(gè)鎖上的自旋時(shí)間 及 鎖的擁有者的狀態(tài)來(lái)決定 。 來(lái)計(jì)算出一個(gè)較為合理的本次自旋等待時(shí)間。

如果 線程1 自旋等待剛剛成功獲得過(guò)鎖,并且占有鎖的 線程2 正在運(yùn)行中,那么JVM就會(huì)認(rèn)為線程1 這次自旋也很有可能再次成功,進(jìn)而允許線程1進(jìn)行更多次的自旋等待。反過(guò)來(lái)說(shuō),如果某個(gè)鎖,自旋很少成功獲得過(guò),那在以后要獲取這個(gè)鎖時(shí)將可能省略掉自旋過(guò)程,以避免浪費(fèi)CPU資源。

2.4.簡(jiǎn)述偏向鎖

  • 本質(zhì): 在無(wú)競(jìng)爭(zhēng)情況下把整個(gè)同步都消除掉,甚至連CAS操作都不做了,只需判斷 Mark Word 中的一些值是否正確就行 。進(jìn)一步提升程序性能。
  • 與輕量級(jí)鎖的區(qū)別:輕量級(jí)鎖是在無(wú)競(jìng)爭(zhēng)的情況下使用 CAS操作 來(lái)代替 互斥同步((阻塞) 的使用,從而實(shí)現(xiàn)同步;而偏向鎖是 在無(wú)競(jìng)爭(zhēng)的情況下完全取消同步 。
  • 與輕量級(jí)鎖的相同點(diǎn):它們都是 樂(lè)觀鎖 ,都認(rèn)為同步期間不會(huì)有其他線程競(jìng)爭(zhēng)鎖。
  • 原理:當(dāng)線程請(qǐng)求到鎖對(duì)象后,將鎖對(duì)象的狀態(tài)標(biāo)志位改為 01 , 即進(jìn)入 “偏向鎖狀態(tài)” 。然后使用 CAS操作 將 線程ID 記錄在鎖對(duì)象的 Mark Word 中。該線程再次請(qǐng)求鎖時(shí),無(wú)需再做任何同步操作,即獲取鎖的過(guò)程只需要檢查 MarkWord 的鎖標(biāo)記位為偏向鎖 以及 當(dāng)前線程 Id 等于 Mark Word 的 ThreadId 即可 直接接進(jìn)入同步塊,連CAS操作都不需要。但是,一旦有第2個(gè)線程需要競(jìng)爭(zhēng)鎖,那么偏向模式立即結(jié)束, 進(jìn)入 “輕量級(jí)鎖” 的狀態(tài)。
  • 優(yōu)點(diǎn):偏向鎖可以 提高有同步但沒(méi)有競(jìng)爭(zhēng)的程序性能 。但是如果鎖對(duì)象時(shí)常被多個(gè)線程競(jìng)爭(zhēng),那偏向鎖就是 多余 的。偏向鎖JDK1.6之后默認(rèn)開(kāi)啟。參數(shù)開(kāi)啟方式:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 在 JDK1.8 中,其實(shí)默認(rèn)是輕量級(jí)鎖 ,但如果設(shè)定了 -XX:BiasedLockingStartupDelay = 0,那在對(duì)一個(gè) Object 做 syncronized 的時(shí)候,會(huì)立即上一把偏向鎖。tips:偏向鎖的“偏”,就是偏心的“偏”、偏袒的“偏”。它的意思是這個(gè)鎖會(huì) 偏向于第一個(gè)獲得它的線程 ,如果在當(dāng)前線程的執(zhí)行過(guò)程中,該鎖沒(méi)有被其他的線程獲取,則持有偏向鎖的線程將永遠(yuǎn)不需要再進(jìn)行同步

2.5.簡(jiǎn)述輕量級(jí)鎖

  • 背景:『輕量級(jí)鎖』是相對(duì)于『重量級(jí)鎖』而言的,即使用操作系統(tǒng)來(lái)實(shí)現(xiàn)的傳統(tǒng)鎖
  • 本質(zhì): 在無(wú)競(jìng)爭(zhēng)的情況下使用CAS操作去取代同步使用的 。
  • 輕量級(jí)鎖與重量級(jí)鎖的區(qū)別:重量級(jí)鎖是一種 悲觀鎖 ,它認(rèn)為總是有多個(gè)線程要競(jìng)爭(zhēng)鎖,所以它每次處理共享數(shù)據(jù)時(shí),不管當(dāng)前系統(tǒng)中是否真的有線程在競(jìng)爭(zhēng)鎖,它都會(huì)使用 互斥同步(阻塞) 來(lái)保證線程的安全;而輕量級(jí)鎖是一種樂(lè)觀鎖,它認(rèn)為鎖存在競(jìng)爭(zhēng)的概率比較小,所以它不使用互斥同步,而是 使用CAS操作來(lái)獲得鎖 ,這樣能 減少傳統(tǒng)的重量級(jí)鎖使用操作系統(tǒng) 『』 帶來(lái)的性能開(kāi)銷。
  • 實(shí)現(xiàn)原理:對(duì)象頭稱為『 Mark Word 』,對(duì)象處于不同的狀態(tài)下, Mark Word 中存儲(chǔ)的信息也所有不同。Mark Word中有個(gè) 標(biāo)志位 用來(lái)表示當(dāng)前對(duì)象所處的狀態(tài)。當(dāng)線程請(qǐng)求鎖時(shí),若該鎖對(duì)象的Mark Word中標(biāo)志位為 01 ( 無(wú)鎖狀態(tài) ),則在該線程的棧幀中創(chuàng)建一塊名為 『鎖記錄Lock Record』 的空間,然后將 鎖對(duì)象的Mark Word拷貝至該空間 ; 最后通過(guò) CAS操作 將鎖對(duì)象的 Mark Word 更新為指向Lock Record的指針若CAS更新指針 成功 ,則輕量級(jí)鎖的上鎖過(guò)程成功;若CAS更新指針 失敗 ,再判斷 當(dāng)前線程是否已經(jīng)持有了該輕量級(jí)鎖 ;若已經(jīng)持有, 直接進(jìn)入同步塊 ;若尚未持有,則表示該鎖已經(jīng)被其他線程占用, 此時(shí)輕量級(jí)鎖就要膨脹成 “重量級(jí)鎖”。
  • 輕量級(jí)鎖比重量級(jí)鎖性能更高的前提 :在輕量級(jí)鎖被占用的整個(gè)同步周期內(nèi),不存在其他線程的競(jìng)爭(zhēng) 。若在該過(guò)程中一旦有其他線程競(jìng)爭(zhēng),那么就會(huì) 膨脹成重量級(jí)鎖 ,從而除了使用以外,還額外發(fā)生了 CAS操作 , 因此在有競(jìng)爭(zhēng)的情況下,輕量級(jí)鎖會(huì)比傳統(tǒng)的重量級(jí)鎖更慢。如果執(zhí)行同步塊的時(shí)間 比較短 ,那么多個(gè)線程之間執(zhí)行使用輕量級(jí)鎖 交替執(zhí)行 。如果執(zhí)行同步塊的時(shí)間 比較長(zhǎng) ,那么多個(gè)線程之間剛開(kāi)始使用輕量級(jí)鎖,后面會(huì) 膨脹為重量級(jí)鎖 。(因?yàn)閳?zhí)行同步塊的時(shí)間長(zhǎng),線程 CAS 自旋獲得輕量級(jí)鎖失敗后就會(huì)鎖膨脹)

2.6.簡(jiǎn)述重量級(jí)鎖

重量級(jí)鎖是一種 悲觀鎖,它認(rèn)為總是有多個(gè)線程要競(jìng)爭(zhēng)鎖,所以它每次處理共享數(shù)據(jù)時(shí),不管當(dāng)前系統(tǒng)中是否真的有線程在競(jìng)爭(zhēng)鎖,它都會(huì)使用 互斥同步(阻塞)來(lái)保證線程的安全;

  • 會(huì)發(fā)生 上下文切換 ,CPU 狀態(tài)從 用戶態(tài)轉(zhuǎn)換為內(nèi)核態(tài) 執(zhí)行操作系統(tǒng)提供的,所以系統(tǒng)開(kāi)銷比較大,響應(yīng)時(shí)間也比較緩慢。

3.鎖升級(jí)

3.1.什么是鎖升級(jí)

鎖升級(jí)的過(guò)程其實(shí)就是對(duì)象頭中的 Mark Word 數(shù)據(jù)結(jié)構(gòu)改變的過(guò)程。 是不可逆轉(zhuǎn)的。

image.png
image.png

1.默認(rèn)是無(wú)鎖狀態(tài)

2.偏向鎖的判斷

  • 在 JDK1.8 中,其實(shí)默認(rèn)是輕量級(jí)鎖 ,但如果設(shè)定了 -XX:BiasedLockingStartupDelay = 0 ,那在無(wú)競(jìng)爭(zhēng)的時(shí)候?qū)σ粋€(gè) Object 做 syncronized 的時(shí)候,會(huì)立即上一把偏向鎖。 當(dāng)處于偏向鎖狀態(tài)時(shí), MarkWork 會(huì)記錄當(dāng)前線程 ID 。

3.升級(jí)到輕量級(jí)鎖的判斷

  • 一旦有 第2個(gè)線程 參與到偏向鎖競(jìng)爭(zhēng)時(shí),會(huì)先判斷 MarkWork 中保存的 線程 ID 是否與這個(gè)線程 ID 相等 , 如果不相等,會(huì)立即撤銷偏向鎖,升級(jí)為輕量級(jí)鎖 。每個(gè)線程在自己的 線程棧中 生成一個(gè) LockRecord ( LR ) ,然后每個(gè)線程通過(guò) CAS (自旋) 的操作將鎖對(duì)象頭中的 MarkWork 設(shè)置為指向自己的 LR 的指針, 哪個(gè)線程設(shè)置成功,就意味著獲得鎖 。

4.升級(jí)到重量級(jí)鎖的判斷

  • 如果鎖競(jìng)爭(zhēng)加劇( 如線程自旋次數(shù)或者自旋的線程數(shù)超過(guò)某閾值, JDK1.6 之后,由 JVM 自己控制該規(guī)則 ),就會(huì)升級(jí)為重量級(jí)鎖 。此時(shí)就會(huì)向操作系統(tǒng)申請(qǐng)資源,線程掛起,進(jìn)入到操作系統(tǒng)內(nèi)核態(tài) 的 等待隊(duì)列 中,等待操作系統(tǒng)調(diào)度,然后映射回 用戶態(tài)在重量級(jí)鎖中,由于 需要做內(nèi)核態(tài)到用戶態(tài)的轉(zhuǎn)換 ,而這個(gè)過(guò)程中需要消耗較多時(shí)間,有可能比用戶執(zhí)行代碼的時(shí)間還要長(zhǎng)。也就是"重"的原因之一。重量級(jí)鎖通過(guò)是對(duì)象內(nèi)部的監(jiān)視器(monitor)實(shí)現(xiàn),其中monitor的本質(zhì)是依賴于底層操作系統(tǒng)的實(shí)現(xiàn)實(shí)現(xiàn).

3.2.鎖升級(jí)的四種鎖狀態(tài)的思路及特點(diǎn)

無(wú)鎖、偏向鎖 、 輕量級(jí)鎖、 重量級(jí)鎖都是指 synchronized在某種場(chǎng)景下的狀態(tài) ,整體的鎖狀態(tài)升級(jí)流程如下:

image.png

Mark Word在不同鎖狀態(tài)下的結(jié)構(gòu)

image.png

無(wú)鎖 VS 偏向鎖 VS 輕量級(jí)鎖 VS 重量級(jí)

image.png

1.無(wú)鎖狀態(tài)

無(wú)鎖沒(méi)有對(duì)共享資源進(jìn)行加鎖,所有的線程都能訪問(wèn)并修改同一個(gè)資源,但同時(shí)只有一個(gè)線程能修改成功。

  • 無(wú)鎖的特點(diǎn) :線程會(huì)不斷自旋的嘗試修改共享資源。如果沒(méi)有沖突就修改成功并退出,否則就會(huì)繼續(xù)循環(huán)嘗試。如果有多個(gè)線程修改同一個(gè)值,必定會(huì)有一個(gè)線程能修改成功,而其他修改失敗的線程會(huì)不斷重試直到修改成功。
  • CAS應(yīng)用即是無(wú)鎖的實(shí)現(xiàn)。無(wú)鎖無(wú)法全面代替有鎖,但無(wú)鎖在某些場(chǎng)合下的性能是非常高的。

2.偏向鎖狀態(tài)

偏向鎖是指一段同步代碼一直被 同一個(gè)線程所訪問(wèn),那么該線程會(huì)自動(dòng)獲取鎖, 減少同一線程獲取鎖的代價(jià),省去了大量有關(guān) 鎖申請(qǐng)的操作。

  • 在大多數(shù)情況下,鎖總是由 同一線程多次獲得 ,不存在多線程競(jìng)爭(zhēng),所以出現(xiàn)了 偏向鎖 。其目的就是 在只有一個(gè)線程執(zhí)行同步代碼塊時(shí)能夠提高性能。

  • 當(dāng)一個(gè)線程訪問(wèn)同步代碼塊并獲取鎖時(shí),會(huì)在 Mark Word 里存儲(chǔ) 鎖偏向的線程ID 。在線程進(jìn)入和退出同步塊時(shí)不再通過(guò)CAS操作來(lái)加鎖和解鎖,而是檢測(cè) Mark Word 里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖 。

  • 引入偏向鎖是為了在 無(wú)多線程競(jìng)爭(zhēng) 的情況下盡量減少不必要的 輕量級(jí)鎖執(zhí)行路徑 ,因?yàn)檩p量級(jí)鎖的獲取及釋放依賴多次 CAS原子指令 ,而偏向鎖只需要在置換 ThreadID 的時(shí)候依賴一次CAS原子指令即可。CAS 原子指令雖然相對(duì)于重量級(jí)鎖來(lái)說(shuō)開(kāi)銷比較小但還是存在非常可觀的本地延遲( 因?yàn)?CAS 的底層是利用 LOCK 指令 + cmpxchg 匯編指令 來(lái)保證原子性的當(dāng)該線程再次請(qǐng)求鎖時(shí),無(wú)需再做任何同步操作,只需要檢查 MarkWord 的鎖標(biāo)記位為偏向鎖 以及 當(dāng)前線程 Id 等于 Mark Word 的 ThreadId 即可

  • 偏向鎖只有遇到其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放鎖,線程不會(huì)主動(dòng)釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(diǎn)(在這個(gè)時(shí)間點(diǎn)上沒(méi)有字節(jié)碼正在執(zhí)行),它會(huì)首先暫停擁有偏向鎖的線程,判斷鎖對(duì)象是否處于被鎖定狀態(tài)。撤銷偏向鎖后恢復(fù)到無(wú)鎖(標(biāo)志位為“01”)或輕量級(jí)鎖(標(biāo)志位為“00”)的狀態(tài)。

3.輕量級(jí)鎖狀態(tài)

是指 當(dāng)鎖是偏向鎖的時(shí)候,被另外的線程所訪問(wèn),偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖,其他線程會(huì)通過(guò) 自旋的形式嘗試獲取鎖,不會(huì)阻塞,從而提高性能。

  • 在代碼進(jìn)入同步塊的時(shí)候,如果同步對(duì)象鎖狀態(tài)為 無(wú)鎖狀態(tài)(鎖標(biāo)志位為“01”狀態(tài),是否為偏向鎖為“0”) ,JVM首先將在當(dāng)前線程的棧幀中建立一個(gè)名為 鎖記錄(Lock Record) 的空間,用于存儲(chǔ)鎖對(duì)象目前的 Mark Word的拷貝 ,然后 拷貝對(duì)象頭中的Mark Word復(fù)制到鎖記錄中。
  • 拷貝成功后,JVM將使用 CAS 操作嘗試將 對(duì)象的Mark Word更新為指向 Lock Record 的指針,并將 Lock Record 里的 owner指針 指向?qū)ο蟮?Mark Word 。如果這個(gè)更新指針操作成功 ,那么這個(gè)線程就擁有了該對(duì)象的鎖,并且對(duì)象Mark Word的鎖標(biāo)志位設(shè)置為 “00” ,表示此對(duì)象處于 輕量級(jí)鎖定狀態(tài)。如果更新操作失敗,JVM首先會(huì)檢查 對(duì)象的Mark Word是否指向當(dāng)前線程的棧幀 ,如果指向就說(shuō)明當(dāng)前線程已經(jīng)擁有了這個(gè)對(duì)象的鎖,那就可以直接進(jìn)入同步塊繼續(xù)執(zhí)行,否則說(shuō)明多個(gè)線程競(jìng)爭(zhēng)鎖。
  • 若 當(dāng)前只有一個(gè)等待線程 ,則該線程通過(guò) 自旋 進(jìn)行等待。但是當(dāng)自旋超過(guò)一定的次數(shù),或者一個(gè)線程在持有鎖,一個(gè)在自旋,又有第三個(gè)來(lái)訪時(shí),輕量級(jí)鎖升級(jí)為 重量級(jí)鎖 。

4.重量級(jí)鎖狀態(tài)

升級(jí)為重量級(jí)鎖時(shí),鎖標(biāo)志的狀態(tài)值變?yōu)?“10” ,此時(shí)Mark Word中存儲(chǔ)的是 指向重量級(jí)鎖的指針 ,此時(shí) 等待鎖的線程 都會(huì)進(jìn)入 阻塞狀態(tài) 。

4.加鎖和解鎖的過(guò)程

4.1.加鎖的過(guò)程

主要分為 3 步:

  1. 在線程進(jìn)入同步塊的時(shí)候,如果同步對(duì)象狀態(tài)為 無(wú)鎖狀態(tài) (鎖標(biāo)志為 01 ),虛擬機(jī)首先將在 當(dāng)前線程 的棧幀中建立一個(gè)名為 鎖記錄( Lock Record) 的空間,用來(lái)存儲(chǔ)鎖對(duì)象目前的 Mark Word 的拷貝 ??截惓晒?,虛擬機(jī)將使用 CAS 操作嘗試 將對(duì)象的 Mark Word 更新為指向 Lock Record 的指針 ,并將 Lock Record 里的 owner 指針 指向鎖對(duì)象的 Mark Word 。如果更新成功,則執(zhí)行 2,否則執(zhí)行 3。
image.png
  1. 如果這個(gè) 更新動(dòng)作成功 了,那么這個(gè)線程就擁有了該對(duì)象的鎖,并且鎖對(duì)象的 Mark Word 中的鎖標(biāo)志位設(shè)置為 "00" ,即表示此對(duì)象處于 輕量級(jí)鎖定狀態(tài) ,這時(shí)候虛擬機(jī) 線程棧與堆中鎖對(duì)象的對(duì)象頭的狀態(tài) 如圖所示。
image.png

3. 如果這個(gè) 更新操作失敗 了,虛擬機(jī)首先會(huì)檢查鎖對(duì)象的 Mark Word 是否指向 當(dāng)前線程的棧幀, 如果是 就說(shuō)明當(dāng)前線程已經(jīng)擁有了這個(gè)對(duì)象的鎖,那就可以 直接進(jìn)入同步塊繼續(xù)執(zhí)行 。 否則說(shuō)明多個(gè)線程競(jìng)爭(zhēng)鎖 ,輕量級(jí)鎖就要膨脹為 重要量級(jí)鎖 ,鎖標(biāo)志的狀態(tài)值變?yōu)?"10" , Mark Word 中存儲(chǔ)的就是指向重量級(jí)鎖的指針 ,后面等待鎖的線程也要進(jìn)入 阻塞狀態(tài)。 而當(dāng)前線程便嘗試使用自旋來(lái)獲取鎖。 自旋失敗后膨脹為重量級(jí)鎖,被阻塞。

4.2.解鎖的過(guò)程

  • 因?yàn)樘摂M機(jī)線程棧幀中的 Displaced Mark Word(鎖記錄LR) 是最初的 無(wú)鎖狀態(tài)時(shí) 的數(shù)據(jù)結(jié)構(gòu),所以 用它來(lái)替換對(duì)象頭中的 Mark Word 就可以釋放鎖 。 如果鎖 已經(jīng)膨脹為重量級(jí) ,此時(shí)是 不可被替換 的,所以替換失敗, 喚醒被掛起的線程 。

5.鎖的優(yōu)缺點(diǎn)

image.png

綜上所述:

  • 偏向鎖 通過(guò)對(duì)比 Mark Word 解決 加鎖 問(wèn)題,避免執(zhí)行CAS操作。
  • 輕量級(jí)鎖 是通過(guò)用 CAS操作 和 自旋 來(lái)解決 加鎖 問(wèn)題,避免線程阻塞和喚醒而影響性能。
  • 重量級(jí)鎖 是 將除了擁有鎖的線程以外的線程都阻塞 。

Java中常見(jiàn)使用 synchroinzed 的地方有:

  • ConcurrentHashMap (jdk 1.8)
  • HashTable
  • StringBuffer

</article>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容