一直想不明白的synchronized鎖竟如此簡(jiǎn)單钦幔!

線程問(wèn)題

線程出現(xiàn)問(wèn)題的根本原因是因?yàn)榫€程上下文切換豆胸,導(dǎo)致線程里的指令沒(méi)有執(zhí)行完就切換執(zhí)行其它線程了杨耙,

舉例

t1和t2線程分別并行執(zhí)行5000次++操作和--操作,理論上結(jié)果應(yīng)該等于0飘痛。

代碼模擬

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(0, 0, 0); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">static int count = 0; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()->{ for (int i = 1;i<5000;i++){ count++; } }); Thread t2 =new Thread(()->{ for (int i = 1;i<5000;i++){ count--; } }); t1.start(); t2.start(); t1.join(); t2.join(); log.debug("count的值是{}",count); } </pre>

實(shí)際count的值有正有負(fù)珊膜,分析i++與i--操作的字節(jié)碼

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(0, 0, 0); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`getstatic i // 獲取靜態(tài)變量i的值
iconst_1 // 準(zhǔn)備常量1
iadd // 自增
putstatic i // 將修改后的值存入靜態(tài)變量i

getstatic i // 獲取靜態(tài)變量i的值
iconst_1 // 準(zhǔn)備常量1
isub // 自減
putstatic i // 將修改后的值存入靜態(tài)變量i` </pre>

可以看到count++count-- 操作實(shí)際都是需要這個(gè)4個(gè)指令完成的,那么這里問(wèn)題就來(lái)了宣脉!Java 的內(nèi)存模型如下车柠,完成靜態(tài)變量的自增,自減需要在主存和工作內(nèi)存中進(jìn)行數(shù)據(jù)交換:

臨界區(qū)

  1. 一個(gè)程序運(yùn)行多線程本身是沒(méi)有問(wèn)題的

  2. 問(wèn)題出現(xiàn)在多個(gè)線程共享資源的時(shí)候

  3. 多個(gè)線程同時(shí)對(duì)共享資源進(jìn)行讀操作本身也沒(méi)有問(wèn)題

  4. 問(wèn)題出現(xiàn)在對(duì)對(duì)共享資源同時(shí)進(jìn)行讀寫操作時(shí)就有問(wèn)題了

  5. 先定義一個(gè)叫做臨界區(qū)的概念:一段代碼內(nèi)如果存在對(duì)共享資源的多線程讀寫操作塑猖,那么稱這段代碼為臨界區(qū)

  6. <pre style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">static int counter = 0; static void increment() {// 臨界區(qū) counter++; } static void decrement() {// 臨界區(qū) counter--; } </pre>

競(jìng)態(tài)條件

多個(gè)線程在臨界區(qū)執(zhí)行竹祷,那么由于代碼指令的執(zhí)行不確定(線程上下文切換)而導(dǎo)致的結(jié)果問(wèn)題,稱為競(jìng)態(tài)條件

synchronized 解決方案

為了避免臨界區(qū)中的競(jìng)態(tài)條件發(fā)生萌庆,由多種手段可以達(dá)到

  • 阻塞式解決方案:synchronized ,Lock
  • 非阻塞式解決方案:原子變量

現(xiàn)在討論使用synchronized來(lái)進(jìn)行解決币旧,即俗稱的對(duì)象鎖践险,它采用互斥的方式讓同一時(shí)刻至多只有一個(gè)線程持有對(duì)象鎖,其他線程如果想獲取這個(gè)鎖就會(huì)阻塞住吹菱,這樣就能保證擁有鎖的線程可以安全的執(zhí)行臨界區(qū)內(nèi)的代碼巍虫,不用擔(dān)心線程上下文切換

注意 雖然 java 中互斥和同步都可以采用 synchronized 關(guān)鍵字來(lái)完成,但它們還是有區(qū)別的:

互斥是保證臨界區(qū)的競(jìng)態(tài)條件發(fā)生鳍刷,同一時(shí)刻只能有一個(gè)線程執(zhí)行臨界區(qū)的代碼

同步是由于線程執(zhí)行的先后占遥,順序不同但是需要一個(gè)線程等待其它線程運(yùn)行到某個(gè)點(diǎn)。

synchronized

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(0, 0, 0); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">synchronized(對(duì)象) // 線程1獲得鎖输瓜, 那么線程2的狀態(tài)是(blocked) { 臨界區(qū) } </pre>

改進(jìn)的代碼

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(0, 0, 0); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`@Slf4j
public class Test03 {
//定義鎖對(duì)象
final private static Object lock = new Object();
static int count = 0;

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 1; i < 5000; i++) {
            //臨界區(qū)加鎖
            synchronized (lock) {
                count++;
            }
        }
    });
    Thread t2 = new Thread(() -> {
        for (int i = 1; i < 5000; i++) {
            //臨界區(qū)加鎖
            synchronized (lock) {
                count--;
            }
        }
    });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    log.debug("count的值是{}", count);
}

}` </pre>

synchronized原理

synchronized實(shí)際上利用對(duì)象保證了臨界區(qū)代碼的原子性猛铅,臨界區(qū)內(nèi)的代碼在外界看來(lái)是不可分割的强岸,不會(huì)被線程切換所打斷。

小結(jié)

關(guān)注點(diǎn)【鎖對(duì)象】,【原子性】

面向?qū)ο笏枷雰?yōu)化

再次改進(jìn)的代碼

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(0, 0, 0); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`@Slf4j
public class Test03 {
public static void main(String[] args) throws InterruptedException {
Room room = new Room();
Thread t1 = new Thread(() -> {
for (int i = 1; i < 5000; i++) {
synchronized (room) {
room.increment();
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 1; i < 5000; i++) {
room.decrease();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("count的值是{}", room.getCounter());
}
}

class Room {
private Integer counter = 0;

public void increment() {
    //臨界區(qū)加鎖
    synchronized (this) {
        counter++;
    }
}

public void decrease() {
    //臨界區(qū)加鎖
    synchronized (this) {
        counter--;
    }
}

public Integer getCounter() {
    synchronized (this) {
        //避免讀取到中間值富俄,加鎖,因?yàn)橛衘oin方法,不加也沒(méi)問(wèn)題
        return counter;
    }
}

}` </pre>

變量的線程安全分析

成員變量和靜態(tài)變量的線程安全分析

  • 如果沒(méi)有變量沒(méi)有在線程間共享蒜胖,那么變量是安全的

  • 如果變量在線程間共享

  • 如果只有讀操作环葵,則線程安全

  • 如果有讀寫操作,則這段代碼是臨界區(qū)嗜愈,需要考慮線程安全

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(0, 0, 0); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`public class Test04 {
ArrayList<Integer> arrayList=new ArrayList<>(); //成員變量

public void method1(int n){
    for (int i = 0; i < n; i++) {
        add();
        remove();
    }
}

public void add(){
    arrayList.add(1); //讀寫操作
}

public void remove(){
    arrayList.remove(0); //讀寫操作
}

}` </pre>

arrayList對(duì)象被兩線程共享旧蛾,執(zhí)行讀寫操作會(huì)出現(xiàn)線程安全問(wèn)題。

局部變量線程安全分析

  • 局部變量【局部變量被初始化為基本數(shù)據(jù)類型】是安全的

    <pre style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">public void test1(){ //局部變量 int類型變量i int i = 10; i++; //讀寫操作 } </pre>

    從圖中可以看到局部變量i并未被兩線程共享蠕嫁。

  • 局部變量引用的對(duì)象未必是安全的
  • 如果局部變量引用的對(duì)象沒(méi)有引用線程共享的對(duì)象锨天,那么是線程安全的

    上述例子改善后的代碼

    <pre style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`public void method1(int n){
    ArrayList<Integer> List=new ArrayList<>(); //局部變量
    for (int i = 0; i < n; i++) {
    add(List);
    remove(List);
    }
    }

    public void add(ArrayList<Integer> arrayList){
    arrayList.add(1); //讀寫操作
    }

    public void remove(ArrayList<Integer> arrayList){
    arrayList.remove(0); //讀寫操作
    }` </pre>

  • 如果局部變量引用的對(duì)象引用了一個(gè)線程共享的對(duì)象,那么要考慮線程安全的

    <pre style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">class Sub extends Test04 { @Override public void add(ArrayList<Integer> arrayList) { Thread sub = new Thread(() -> arrayList.add(1)); sub.start(); } } </pre>

    如果子類創(chuàng)建了新的線程重寫了父類的方法剃毒,那么又會(huì)造成線程安全問(wèn)題绍绘,子類創(chuàng)建的線程會(huì)共享其中一個(gè)線程的局部變量,為避免此類問(wèn)題,可加final或private關(guān)鍵字修飾陪拘,使得該方法不會(huì)被繼承或訪問(wèn)厂镇。

    常見(jiàn)線程安全類

    注意:

    這里說(shuō)它們是線程安全的是指,多個(gè)線程調(diào)用它們<!!!同一個(gè)實(shí)例>的某個(gè)方法時(shí)左刽,是線程安全的捺信。也可以理解為它

    們的每個(gè)方法是原子的,即每個(gè)方法都加了synchronized關(guān)鍵字欠痴,但是組合在一起線程就不安全了迄靠。

    <pre style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">Hashtable table = new Hashtable(); new Thread(()->{ table.put("key", "value1"); }).start(); //安全 new Thread(()->{ table.put("key", "value2"); }).start(); //安全 </pre>

    線程安全類方法的組合

    注意多個(gè)線程調(diào)用同一實(shí)例(table)的讀寫方法的組合(get方法和put方法組合)不是原子的

    <pre style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">Hashtable table = new Hashtable(); // 線程1,線程2 if( table.get("key") == null) { table.put("key", value); } </pre>

    結(jié)論:輸出table中的key值喇辽,結(jié)果可能是v1也可能是v2掌挚,組合方法導(dǎo)致線程不安全。

  1. String
  2. Integer
  3. StringBuffer
  4. Random
  5. Vector
  6. Hashtable
  7. java.util.concurrent 包下的類

不可變類的線程安全

StringInteger類都是不可變的類菩咨,因?yàn)槠漕悆?nèi)部狀態(tài)是不可改變的吠式,因此它們的方法都是線程安全的,有同

學(xué)或許有疑問(wèn)抽米,Stringreplace特占,substring 等方法【可以】改變值啊,其實(shí)調(diào)用這些方法返回的已經(jīng)是

一個(gè)新創(chuàng)建的對(duì)象了云茸!

習(xí)題一

方法中有多個(gè)實(shí)例對(duì)象的臨界區(qū)鎖住this無(wú)效是目。

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(0, 0, 0); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`@Slf4j
public class Test05 {
public static void main(String[] args) throws InterruptedException {
Account A = new Account(1000);
Account B = new Account(1000);
Thread T1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
A.transferMoney(B, randomMoney());
}
});
Thread T2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
B.transferMoney(A, randomMoney());
}
});
T1.start();
T2.start();
T1.join();
T2.join();

    log.debug("總錢數(shù)為{}", A.getMoney() + B.getMoney());
}

static Random random = new Random();

static public int randomMoney() {
    return random.nextInt(100) + 1;
}

}

class Account {
private int money;

public Account(int money) {
    this.money = money;
}

public int getMoney() {
    return money;
}

public Account() {
}

public void setMoney(int money) {
    this.money = money;
}

public void transferMoney(Account target, int money) {
        if (this.money >= money){
            this.setMoney(this.getMoney()-money);
            target.setMoney(target.getMoney() + money);
    }
}

}` </pre>

分析

線程t1和t2共享Account類中的成員變量money,且調(diào)用方法中包含讀寫操作(臨界區(qū))标捺,因此會(huì)有線程安全問(wèn)題懊纳,但

是在transferMoney方法中添加synchronized關(guān)鍵字相當(dāng)于

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(0, 0, 0); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">public void transferMoney(Account target, int money) { synchronized (this){ if (this.money >= money){ this.setMoney(this.getMoney()-money); target.setMoney(target.getMoney() + money); } }} </pre>

此時(shí)線程并不安全,因?yàn)閠arget對(duì)象的money變量并未加鎖亡容,t1和t2讀寫操作還仍然可以訪問(wèn)并修改target對(duì)象的成員

變量money长踊,造成線程不安全。

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(0, 0, 0); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">public void transferMoney(Account target, int money) { synchronized (Account.class){ if (this.money >= money){ this.setMoney(this.getMoney()-money); target.setMoney(target.getMoney() + money); } }} </pre>

注意:

此次可以采取鎖住類對(duì)象解決萍倡,target和this都是類對(duì)象的實(shí)例身弊,因此兩個(gè)對(duì)象中的成員變量都會(huì)被鎖住,線程安全列敲,

但是如果有多個(gè)無(wú)關(guān)實(shí)例對(duì)象也會(huì)被同時(shí)鎖住阱佛,處于阻塞狀態(tài),就會(huì)導(dǎo)致效率低下戴而。

Monitor 概念

Java 對(duì)象頭

  • Mark Word 包含對(duì)象hashcode值凑术,年齡代,鎖狀態(tài)等信息
  • Klass Word 包含對(duì)象的類型信息

以 32 位虛擬機(jī)為例,普通對(duì)象的對(duì)象頭結(jié)構(gòu)如下所意,其中的Klass Word為指針淮逊,指向?qū)?yīng)的Class對(duì)象催首;

數(shù)組對(duì)象

其中 Mark Word 結(jié)構(gòu)為

<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1583651590160</figcaption>

所以一個(gè)對(duì)象的結(jié)構(gòu)如下:

<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1583678624634</figcaption>

原理

Monitor被翻譯為監(jiān)視器或者說(shuō)管程

每個(gè)java對(duì)象都可以關(guān)聯(lián)一個(gè)Monitor,如果使用synchronized給對(duì)象上鎖(重量級(jí))泄鹏,該對(duì)象頭的Mark Word中就被設(shè)置為指向Monitor對(duì)象的指針

  • 剛開始時(shí)Monitor中的Owner為null
  • 當(dāng)Thread-2 執(zhí)行synchronized(obj){}代碼時(shí)就會(huì)將Monitor的所有者Owner 設(shè)置為 Thread-2郎任,上鎖成功,Monitor中同一時(shí)刻只能有一個(gè)Owner
  • 當(dāng)Thread-2 占據(jù)鎖時(shí)备籽,如果線程Thread-3舶治,Thread-4也來(lái)執(zhí)行synchronized(obj){}代碼,就會(huì)進(jìn)入EntryList中變成BLOCKED狀態(tài)
  • Thread-2 執(zhí)行完同步代碼塊的內(nèi)容车猬,然后喚醒 EntryList 中等待的線程來(lái)競(jìng)爭(zhēng)鎖霉猛,競(jìng)爭(zhēng)時(shí)是非公平的
  • 圖中 WaitSet 中的 Thread-0,Thread-1 是之前獲得過(guò)鎖珠闰,但條件不滿足進(jìn)入 WAITING 狀態(tài)的線程惜浅,后面講wait-notify 時(shí)會(huì)分析

注意:synchronized 必須是進(jìn)入同一個(gè)對(duì)象的 monitor 才有上述的效果不加 synchronized 的對(duì)象不會(huì)關(guān)聯(lián)監(jiān)視器,不遵從以上規(guī)則

synchronized原理

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(0, 0, 0); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">static final Object lock=new Object(); static int counter = 0; public static void main(String[] args) { synchronized (lock) { counter++; } } </pre>

反編譯后的部分字節(jié)碼

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(0, 0, 0); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;"> `0 getstatic #2 <com/concurrent/test/Test17.lock>

取得lock的引用(synchronized開始了)

3 dup

復(fù)制操作數(shù)棧棧頂?shù)闹捣湃霔m敺龋磸?fù)制了一份lock的引用

4 astore_1

操作數(shù)棧棧頂?shù)闹祻棾鎏诚ぃ磳ock的引用存到局部變量表中

5 monitorenter

將lock對(duì)象的Mark Word置為指向Monitor指針

6 getstatic #3 <com/concurrent/test/Test17.counter>
9 iconst_1
10 iadd
11 putstatic #3 <com/concurrent/test/Test17.counter>
14 aload_1

從局部變量表中取得lock的引用,放入操作數(shù)棧棧頂

15 monitorexit

將lock對(duì)象的Mark Word重置阅仔,喚醒EntryList

16 goto 24 (+8)

下面是異常處理指令吹散,可以看到弧械,如果出現(xiàn)異常八酒,也能自動(dòng)地釋放鎖

19 astore_2
20 aload_1
21 monitorexit
22 aload_2
23 athrow
24 return` </pre>

注意:方法級(jí)別的 synchronized 不會(huì)在字節(jié)碼指令中有所體現(xiàn)

重量級(jí)鎖優(yōu)化

輕量級(jí)鎖

輕量級(jí)鎖的使用場(chǎng)景是:如果一個(gè)對(duì)象雖然有多個(gè)線程要對(duì)它進(jìn)行加鎖,但是加鎖的時(shí)間是錯(cuò)開的(也就是沒(méi)有人可以競(jìng)爭(zhēng)的)刃唐,那么可以使用輕量級(jí)鎖來(lái)進(jìn)行優(yōu)化羞迷。輕量級(jí)鎖對(duì)使用者是透明的,即語(yǔ)法仍然是synchronized画饥,假設(shè)有兩個(gè)方法同步塊衔瓮,利用同一個(gè)對(duì)象加鎖

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; color: rgb(0, 0, 0); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">static final Object obj = new Object(); public static void method1() { synchronized( obj ) { // 同步塊 A method2(); } } public static void method2() { synchronized( obj ) { // 同步塊 B } } </pre>

  1. 每次指向到synchronized代碼塊時(shí),都會(huì)創(chuàng)建鎖記錄(Lock Record)對(duì)象抖甘,每個(gè)線程都會(huì)包括一個(gè)鎖記錄的結(jié)構(gòu)热鞍,鎖記錄內(nèi)部可以儲(chǔ)存對(duì)象的Mark Word和對(duì)象引用reference

  2. 圖片

    <figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1583755737580</figcaption>

  3. 讓鎖記錄中的Object reference指向?qū)ο螅⑶覈L試用cas(compare and sweep)替換Object對(duì)象的Mark Word 衔彻,將Mark Word 的值存入鎖記錄中

  4. 圖片

    <figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1583755888236</figcaption>

  5. 如果cas替換成功薇宠,那么對(duì)象的對(duì)象頭儲(chǔ)存的就是鎖記錄的地址和狀態(tài)01,如下所示

  6. <figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1583755964276</figcaption>

  7. 如果cas失敗艰额,有兩種情況

  8. <figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1583756190177</figcaption>

  9. 如果是其它線程已經(jīng)持有了該Object的輕量級(jí)鎖澄港,那么表示有競(jìng)爭(zhēng),將進(jìn)入鎖膨脹階段

  10. 如果是自己的線程已經(jīng)執(zhí)行了synchronized進(jìn)行加鎖柄沮,那么那么再添加一條 Lock Record 作為重入的計(jì)數(shù)

  11. 當(dāng)線程退出synchronized代碼塊的時(shí)候回梧,**如果獲取的是取值為 null 的鎖記錄 **废岂,表示有重入,這時(shí)重置鎖記錄狱意,表示重入計(jì)數(shù)減一

  12. <figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1583756357835</figcaption>

  13. 當(dāng)線程退出synchronized代碼塊的時(shí)候湖苞,如果獲取的鎖記錄取值不為 null,那么使用cas將Mark Word的值恢復(fù)給對(duì)象

  14. 成功則解鎖成功

  15. 失敗髓涯,則說(shuō)明輕量級(jí)鎖進(jìn)行了鎖膨脹或已經(jīng)升級(jí)為重量級(jí)鎖袒啼,進(jìn)入重量級(jí)鎖解鎖流程

鎖膨脹

如果在嘗試加輕量級(jí)鎖的過(guò)程中,cas操作無(wú)法成功纬纪,這是有一種情況就是其它線程已經(jīng)為這個(gè)對(duì)象加上了輕量級(jí)鎖蚓再,這是就要進(jìn)行鎖膨脹,將輕量級(jí)鎖變成重量級(jí)鎖包各。

  1. 當(dāng) Thread-1 進(jìn)行輕量級(jí)加鎖時(shí)摘仅,Thread-0 已經(jīng)對(duì)該對(duì)象加了輕量級(jí)鎖

  2. <figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1583757433691</figcaption>

  3. 這時(shí) Thread-1 加輕量級(jí)鎖失敗,進(jìn)入鎖膨脹流程

  4. 即為對(duì)象申請(qǐng)Monitor鎖问畅,讓Object指向重量級(jí)鎖地址娃属,然后自己進(jìn)入Monitor 的EntryList 變成BLOCKED狀態(tài)

  5. <figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1583757586447</figcaption>

  6. 當(dāng)Thread-0 推出synchronized同步塊時(shí),使用cas將Mark Word的值恢復(fù)給對(duì)象頭护姆,失敗矾端,那么會(huì)進(jìn)入重量級(jí)鎖的解鎖過(guò)程,即按照Monitor的地址找到Monitor對(duì)象卵皂,將Owner設(shè)置為null秩铆,喚醒EntryList 中的Thread-1線程

自旋優(yōu)化

重量級(jí)鎖競(jìng)爭(zhēng)的時(shí)候,還可以使用自旋來(lái)進(jìn)行優(yōu)化灯变,如果當(dāng)前線程自旋成功(即在自旋的時(shí)候持鎖的線程釋放了鎖)殴玛,那么當(dāng)前線程就可以不用進(jìn)行上下文切換就獲得了鎖

  1. 自旋重試成功的情況

  2. <figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1583758113724</figcaption>

  3. 自旋重試失敗的情況,自旋了一定次數(shù)還是沒(méi)有等到持鎖的線程釋放鎖

  4. <figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1583758136650</figcaption>

自旋會(huì)占用 CPU 時(shí)間添祸,單核 CPU 自旋就是浪費(fèi)滚粟,多核 CPU 自旋才能發(fā)揮優(yōu)勢(shì)。

智能控制自選可能性

在 Java 6 之后自旋鎖是自適應(yīng)的刃泌,比如對(duì)象剛剛的一次自旋操作成功過(guò)凡壤,那么認(rèn)為這次自旋成功的可能性會(huì)高,就多自旋幾次耙替;

反之亚侠,就少自旋甚至不自旋,總之林艘,比較智能盖奈。Java 7 之后不能控制是否開啟自旋功能

偏向鎖

在輕量級(jí)的鎖中,我們可以發(fā)現(xiàn)狐援,如果同一個(gè)線程對(duì)同一個(gè)2對(duì)象進(jìn)行重入鎖時(shí)钢坦,也需要執(zhí)行CAS操作究孕,這是有點(diǎn)耗時(shí)

滴,那么Java6開始引入了偏向鎖概念爹凹,只有第一次使用CAS時(shí)將對(duì)象的Mark Word頭設(shè)置為入鎖線程ID(操作系統(tǒng)提供)

之后這個(gè)入鎖線程再進(jìn)行重入鎖時(shí)厨诸,發(fā)現(xiàn)線程ID是自己的,那么就不用再進(jìn)行CAS了

偏向狀態(tài)

<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1583762169169</figcaption>

一個(gè)對(duì)象的創(chuàng)建過(guò)程

  1. 如果開啟了偏向鎖(默認(rèn)是開啟的)禾酱,那么對(duì)象剛創(chuàng)建之后微酬,Mark Word 最后三位的值101,并且這是它的Thread颤陶,epoch颗管,age都是0,在加鎖的時(shí)候進(jìn)行設(shè)置這些的值.

  2. 偏向鎖默認(rèn)是延遲的滓走,不會(huì)在程序啟動(dòng)的時(shí)候立刻生效垦江,如果想避免延遲,可以添加虛擬機(jī)參數(shù)來(lái)禁用延遲:-XX:BiasedLockingStartupDelay=0來(lái)禁用延遲

  3. 注意:處于偏向鎖的對(duì)象解鎖后搅方,線程 ID(不能用Java方法獲取是操作系統(tǒng)提供的)仍存儲(chǔ)于對(duì)象頭中

  4. 加上虛擬機(jī)參數(shù)-XX:BiasedLockingStartupDelay=0進(jìn)行測(cè)試

  5. 輸出結(jié)果如下比吭,三次輸出的狀態(tài)碼都為101

  6. <pre style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">public static void main(String[] args) throws InterruptedException { Test1 t = new Test1(); test.parseObjectHeader(getObjectHeader(t)); synchronized (t){ test.parseObjectHeader(getObjectHeader(t)); } test.parseObjectHeader(getObjectHeader(t)); } </pre>

    <pre style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">biasedLockFlag (1bit): 1 LockFlag (2bit): 01 biasedLockFlag (1bit): 1 LockFlag (2bit): 01 biasedLockFlag (1bit): 1 LockFlag (2bit): 01 </pre>

禁用偏向鎖

測(cè)試禁用:如果沒(méi)有開啟偏向鎖姨涡,那么對(duì)象創(chuàng)建后最后三位的值為001衩藤,這時(shí)候它的hashcode,age都為0涛漂,hashcode

是第一次用到hashcode時(shí)才賦值的赏表。在上面測(cè)試代碼運(yùn)行時(shí)在添加 VM 參數(shù)-XX:-UseBiasedLocking禁用偏向鎖

(禁用偏向鎖則優(yōu)先使用輕量級(jí)鎖),退出synchronized狀態(tài)變回001

  1. 虛擬機(jī)參數(shù)-XX:-UseBiasedLocking

  2. 輸出結(jié)果如下怖喻,最開始狀態(tài)為001底哗,然后加輕量級(jí)鎖變成00岁诉,最后恢復(fù)成001

    <pre style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">biasedLockFlag (1bit): 0 LockFlag (2bit): 01 LockFlag (2bit): 00 biasedLockFlag (1bit): 0 LockFlag (2bit): 01 </pre>

撤銷偏向鎖-【鎖對(duì)象調(diào)用hashcode方法】

測(cè)試 hashCode:當(dāng)調(diào)用對(duì)象的hashcode方法的時(shí)候就會(huì)撤銷這個(gè)對(duì)象的偏向鎖锚沸,因?yàn)槭褂闷蜴i時(shí)沒(méi)有位置存

hashcode的值了。

僅限于偏向鎖

  • 輕量級(jí)鎖會(huì)將hashcode值
  1. 測(cè)試代碼如下涕癣,使用虛擬機(jī)參數(shù)-XX:BiasedLockingStartupDelay=0 哗蜈,確保我們的程序最開始使用了偏向鎖!但是結(jié)果顯示程序還是使用了輕量級(jí)鎖坠韩。

    <pre style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`public static void main(String[] args) throws InterruptedException {
    Test1 t = new Test1();
    t.hashCode();
    test.parseObjectHeader(getObjectHeader(t));

        synchronized (t){
            test.parseObjectHeader(getObjectHeader(t));
        }
        test.parseObjectHeader(getObjectHeader(t));
    }` </pre>
    
  2. 輸出結(jié)果

    <pre style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">biasedLockFlag (1bit): 0 LockFlag (2bit): 01 LockFlag (2bit): 00 biasedLockFlag (1bit): 0 LockFlag (2bit): 01 </pre>

撤銷偏向鎖-【其它線程使用該鎖對(duì)象】

這里我們演示的是偏向鎖撤銷變成輕量級(jí)鎖的過(guò)程距潘,那么就得滿足輕量級(jí)鎖的使用條件,就是沒(méi)有線程對(duì)同一個(gè)對(duì)象進(jìn)行鎖競(jìng)爭(zhēng)只搁,我們使用waitnotify 來(lái)輔助實(shí)現(xiàn)

  1. 代碼音比,虛擬機(jī)參數(shù)-XX:BiasedLockingStartupDelay=0確保我

    們的程序最開始使用了偏向鎖!

  2. 輸出結(jié)果氢惋,最開始使用的是偏向鎖洞翩,但是第二個(gè)線程嘗試獲取對(duì)象

    鎖時(shí)稽犁,發(fā)現(xiàn)本來(lái)對(duì)象偏向的是線程一,那么偏向鎖就會(huì)失效骚亿,加的

    就是輕量級(jí)鎖

    <pre style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">biasedLockFlag (1bit): 1 LockFlag (2bit): 01 biasedLockFlag (1bit): 1 LockFlag (2bit): 01 biasedLockFlag (1bit): 1 LockFlag (2bit): 01 biasedLockFlag (1bit): 1 LockFlag (2bit): 01 LockFlag (2bit): 00 biasedLockFlag (1bit): 0 LockFlag (2bit): 01 </pre>

撤銷 - 調(diào)用 wait/notify

會(huì)使對(duì)象的鎖變成重量級(jí)鎖已亥,因?yàn)閣ait/notify方法之后重量級(jí)鎖才支持

批量重偏向

如果對(duì)象被多個(gè)線程訪問(wèn),但是沒(méi)有競(jìng)爭(zhēng)来屠,這時(shí)候偏向了線程一的對(duì)象仍有機(jī)會(huì)重新偏向線程二虑椎,會(huì)重置其的線程ID。

重新偏向條件:當(dāng)撤銷偏向鎖 >= 20次時(shí)

當(dāng)撤銷偏向鎖閾值超過(guò)40次后俱笛,該類中所有的實(shí)例對(duì)象都會(huì)變?yōu)椴豢善虻摹?/p>

批量撤銷偏向條件:當(dāng)撤銷偏向鎖 >= 40次時(shí)

鎖消除

Java運(yùn)行時(shí)有JIT即時(shí)編譯器捆姜,會(huì)對(duì)Java的字節(jié)碼進(jìn)行進(jìn)一步優(yōu)化,如對(duì)熱點(diǎn)代碼進(jìn)行優(yōu)化迎膜,在優(yōu)化過(guò)程中也會(huì)分析變量

是否會(huì)被共享娇未,如果不可能被共享,就不會(huì)執(zhí)行 synchronized 關(guān)鍵字

總結(jié)

Java6開始星虹,鎖根據(jù)不同情況進(jìn)行了優(yōu)化零抬,偏向鎖(默認(rèn)延時(shí))--> 輕量級(jí)鎖 --> (鎖自旋) --> 重量級(jí)鎖

wait和notify

  • Owener發(fā)現(xiàn)條件不滿足,調(diào)用wait方法宽涌,即可進(jìn)入WitSet變?yōu)閃AITING狀態(tài)
  • BLOCKERD和WAITING狀態(tài)的線程都處于堵塞狀態(tài)平夜,不占用CPU時(shí)間片
  • BLOCKED線程會(huì)在Owner線程釋放鎖時(shí)喚醒
  • WAITING線程會(huì)在Owner線程調(diào)用notify或notifyAll時(shí)喚醒,但喚醒后并不意味著立刻獲得鎖卸亮,仍需要進(jìn)入EntryList重新競(jìng)爭(zhēng)鎖

API介紹

  • obj.wait()讓進(jìn)入Object監(jiān)視器的線程到waitSet等待忽妒,如果不喚醒一直等待
  • obj.wait(long timeout) 讓進(jìn)入Object監(jiān)視器的線程到waitSet等待,即使不喚醒超時(shí)也會(huì)被喚醒
  • obj.notify()在Object上正在waitSet等待的線程中挑一個(gè)線程喚醒
  • obj.notifyAll()讓Object上正在waitSet等待的線程的所有線程全部喚醒

前提:這些都是線程之間進(jìn)行協(xié)作的手段兼贸,都屬于Object對(duì)象的方法段直,調(diào)用前必須獲得此對(duì)象的鎖。

正確使用方法

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`final static Object lock = new Object(); //final避免修改
synchronized (lock){
while(條件不成立){ //所有線程都喚醒后但條件仍可能不成立溶诞,while循環(huán)可以再次等待
lock.wait();
}
//繼續(xù)干活
}

synchronized (lock){
lock.notifyAll(); //避免喚醒錯(cuò)誤線程
}` </pre>

對(duì)比sleep(long n) 和 wait(long n)

  1. sleep是Thread方法鸯檬,而是Object的方法
  2. sleep不需要強(qiáng)制和synchronized配合使用,但wait需要
  3. sleep在睡眠的同時(shí)不會(huì)釋放對(duì)象鎖螺垢,但wait在等待時(shí)候會(huì)釋放對(duì)象鎖
  4. 線程的狀態(tài)都是TIME_WAIT

同步模式之保護(hù)性暫停

即 Guarded Suspension喧务,用在一個(gè)線程等待另一個(gè)線程的執(zhí)行結(jié)果,要點(diǎn):

  1. 有一個(gè)結(jié)果需要從一個(gè)線程傳遞到另一個(gè)線程枉圃,讓他們關(guān)聯(lián)同一個(gè) GuardedObject
  2. 如果有結(jié)果不斷從一個(gè)線程到另一個(gè)線程那么可以使用消息隊(duì)列(見(jiàn)生產(chǎn)者/消費(fèi)者)
  3. JDK 中功茴,join 的實(shí)現(xiàn)、Future 的實(shí)現(xiàn)孽亲,采用的就是此模式
  4. 因?yàn)橐却硪环降慕Y(jié)果坎穿,因此歸類到同步模式

<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1594473284105</figcaption>

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`@Slf4j
public class Main {
public static void main(String[] args) throws InterruptedException {
GuardedObject guardedObject = new GuardedObject();
new Thread("t1"){
@Override
public void run() {
log.debug("等待結(jié)果...");
Object o = guardedObject.get(2000);
log.debug("獲得了結(jié)果{}",o);
}
}.start();
Thread.sleep(1000);
log.debug("執(zhí)行下載");
// guardedObject.complete(new Object());
guardedObject.complete(null); //虛假喚醒,喚醒線程但條件仍不滿足
}
}

class GuardedObject {
// 結(jié)果
private Object response;

//獲取結(jié)果
//timeout表示要等待多久
public synchronized Object get(long timeout) {
    long begin = System.currentTimeMillis();
    long passedTime = 0;
    while (response == null) {
        long waitTime = timeout - passedTime;
        if (waitTime <= 0)
            break;
        else {
            try {
                this.wait(waitTime);//參數(shù)為waitTime,避免虛假喚醒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //求得經(jīng)歷時(shí)間
            passedTime = System.currentTimeMillis() - begin;
        }
    }
    return response;
}

//產(chǎn)生結(jié)果
public synchronized void complete(Object response){
    this.response =response;
    this.notifyAll();
}

}` </pre>

join方法

關(guān)于超時(shí)的增強(qiáng),在join(long millis) 的源碼中得到了體現(xiàn):

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
    throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) { //join()底層就是調(diào)用了wait(0)但是不是無(wú)限等待而是等待線程代碼執(zhí)行結(jié)束
    while (isAlive()) {
        wait(0);
    }
} else {
// join一個(gè)指定的時(shí)間
    while (isAlive()) {
        long delay = millis - now;
        if (delay <= 0) {
            break;
        }
        wait(delay);
        now = System.currentTimeMillis() - base;
    }
}

}` </pre>

多任務(wù)版 GuardedObject

圖中 Futures 就好比居民樓一層的信箱(每個(gè)信箱有房間編號(hào))玲昧,

左側(cè)的 t0犯祠,t2,t4 就好比等待郵件的【居民】酌呆,

右側(cè)的 t1衡载,t3,t5 就好比【郵遞員】

如果需要在多個(gè)類之間使用 GuardedObject 對(duì)象隙袁,作為參數(shù)傳遞不是很方便痰娱,

因此設(shè)計(jì)一個(gè)用來(lái)解耦的中間類(信箱),這樣不僅能夠解耦【結(jié)果等待者】和【結(jié)果生產(chǎn)者】菩收,還能夠同時(shí)支持多個(gè)任務(wù)的管理梨睁。

和生產(chǎn)者消費(fèi)者模式的區(qū)別就是:這個(gè)生產(chǎn)者和消費(fèi)者之間是一一對(duì)應(yīng)的關(guān)系,但是生產(chǎn)者消費(fèi)者模式并不是娜饵。rpc框

架的調(diào)用中就使用到了這種模式坡贺。

<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1594518049426</figcaption>

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`@Slf4j
public class Main {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new People().start();
}
Thread.sleep(1000);
for (Integer id : Mailbox.getIds()) {
new Postman(id,"內(nèi)容"+id).start();
}
}
}
@Slf4j
class Postman extends Thread{
private int id;
private String mail;

public Postman(int id, String mail) {
    this.id = id;
    this.mail = mail;
}

@Override
public void run() {
    GuardedObject guardedObject = Mailbox.getGuardedObject(id);
    log.debug("開始送信...,id為{},內(nèi)容為{}",id,mail);
    guardedObject.complete(mail);
}

}

@Slf4j
class People extends Thread {
@Override
public void run() {
//收信
GuardedObject guardedObject = Mailbox.createdObject();
log.debug("開始收信 id:{}", guardedObject.getId());
Object mail = guardedObject.get(5000);
log.debug("收到id為{},結(jié)果是{}",guardedObject.getId(),mail);
}
}

class Mailbox {
private static Hashtable<Integer, GuardedObject> boxes = new Hashtable<>();

private static int id = 1;

private synchronized static int generatedId() {
    return id++;
}

public static GuardedObject getGuardedObject(int id) {
    return boxes.remove(id);
}

public static GuardedObject createdObject() {
    GuardedObject object = new GuardedObject(generatedId());
    boxes.put(object.getId(), object);
    return object;
}

public static Set<Integer> getIds() {
    return boxes.keySet();
}

}

class GuardedObject {
private int id;

public int getId() {
    return id;
}

public GuardedObject(int id) {
    this.id = id;
}

// 結(jié)果
private Object mail;

//獲取結(jié)果
//timeout表示要等待多久
public synchronized Object get(long timeout) {
    long begin = System.currentTimeMillis();
    long passedTime = 0;
    while (mail == null) {
        long waitTime = timeout - passedTime;
        if (waitTime <= 0)
            break;
        else {
            try {
                this.wait(waitTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //求得經(jīng)歷時(shí)間
            passedTime = System.currentTimeMillis() - begin;
        }
    }
    return mail;
}

//產(chǎn)生結(jié)果
public synchronized void complete(Object mail){
    this.mail =mail;
    this.notifyAll();
}

}` </pre>

異步模式之生產(chǎn)/消費(fèi)者

要點(diǎn)

  1. 與前面的保護(hù)性暫停中的 GuardObject 不同,不需要產(chǎn)生結(jié)果和消費(fèi)結(jié)果的線程一一對(duì)應(yīng)
  2. 消費(fèi)隊(duì)列可以用來(lái)平衡生產(chǎn)和消費(fèi)的線程資源
  3. 生產(chǎn)者僅負(fù)責(zé)產(chǎn)生結(jié)果數(shù)據(jù)箱舞,不關(guān)心數(shù)據(jù)該如何處理遍坟,而消費(fèi)者專心處理結(jié)果數(shù)據(jù)
  4. 消息隊(duì)列是有容量限制的,滿時(shí)不會(huì)再加入數(shù)據(jù)晴股,空時(shí)不會(huì)再消耗數(shù)據(jù)
  5. JDK 中各種阻塞隊(duì)列愿伴,采用的就是這種模式

“異步”的意思就是生產(chǎn)者產(chǎn)生消息之后消息沒(méi)有被立刻消費(fèi),而“同步模式”中电湘,消息在產(chǎn)生之后被立刻消費(fèi)了隔节。

<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1594524622020</figcaption>

注意:我們寫一個(gè)線程間通信的消息隊(duì)列,要注意區(qū)別寂呛,像rabbit mq等消息框架是進(jìn)程間通信的怎诫。

代碼實(shí)現(xiàn)

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`@Slf4j
public class Main {
public static void main(String[] args) {
//創(chuàng)建容量為2的雙向鏈表
MessageQueue messageQueue = new MessageQueue(2);
for (int i = 1; i <= 3; i++) {
int id = i;
new Thread(() -> {
while (true) {
log.debug(id + "號(hào)生產(chǎn)者開始生產(chǎn)了...");
messageQueue.put(new Message(id, "內(nèi)容" + id));
}
}).start();
}

    new Thread(() -> {
        while (true) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("消費(fèi)開始消費(fèi)了...");
            Message message = messageQueue.take();
            log.debug("消費(fèi)的id為{}內(nèi)容為{}",message.getId(),message.getValue());
        }
    }).start();
}

}

@Slf4j
class MessageQueue {
private LinkedList<Message> list = new LinkedList();

private int capcity;

public MessageQueue(int capcity) {
    this.capcity = capcity;
}

//獲取消息
public Message take() {
    synchronized (list) {
        while (list.isEmpty()) {
            try {
                log.debug("隊(duì)列為空,消費(fèi)者線程開始阻塞...");
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //從頭部取消息
        Message message = list.removeFirst();
        //通知生產(chǎn)者可以生產(chǎn)了
        log.debug("生產(chǎn)者可以生產(chǎn)了");
        list.notifyAll();
        return message;
    }
}

//存入消息
public void put(Message message) {
    synchronized (list) {
        while (list.size() == capcity) {
            try {
                log.debug("隊(duì)列已滿贷痪,生產(chǎn)者線程開始阻塞...");
                list.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //從隊(duì)列尾部加入
        list.addLast(message);
        //通知消費(fèi)者可以消費(fèi)了
        log.debug("消費(fèi)者可以消費(fèi)了");
        list.notifyAll();
    }
}

}

final class Message {
private int id;
private Object value;

public Message(int id, Object value) {
    this.id = id;
    this.value = value;
}

public Object getValue() {
    return value;
}

@Override
public String toString() {
    return "Message{" +
            "id=" + id +
            ", value=" + value +
            '}';
}

public int getId() {
    return id;
}

}` </pre>

park & unpack

基本使用

它們是 LockSupport 類中的方法

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()->{ log.debug("begin..."); log.debug("park..."); LockSupport.park(); log.debug("resume..."); },"t1"); t1.start(); Thread.sleep(1000); log.debug("unpark..."); LockSupport.unpark(t1); //t1線程解鎖 } </pre>

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">20:59:18.776 [t1] DEBUG com.Long.Park.Park - begin... 20:59:18.791 [t1] DEBUG com.Long.Park.Park - park... 20:59:19.789 [main] DEBUG com.Long.Park.Park - unpark... 20:59:19.789 [t1] DEBUG com.Long.Park.Park - resume... </pre>

特點(diǎn)

與Object的wait & notify 相比

  • wait幻妓,notify 和 notifyAll 必須配合Object Monitor 一起使用,而 park & unpark 不必

  • park & unpark 是以線程為單位 【阻塞】和【喚醒】線程呢诬,而

    notify 只能隨機(jī)喚醒一個(gè)等待線程涌哲,而 notifyAll 是喚醒所有等

    待線程胖缤,就不那么精確尚镰。

  • park & unpark 可以先unpark,而wait & notify不能先 notify 和 notifyAll

原理

每個(gè)線程都有自己的一個(gè) Parker 對(duì)象哪廓,由三部分組成 _counter狗唉, _cond和 _mutex

  1. 打個(gè)比喻線程就像一個(gè)旅人,Parker 就像他隨身攜帶的背包涡真,條件變量 _ cond就好比背包中的帳篷分俯。_counter 就好比背包中的備用干糧(0 為耗盡肾筐,1 為充足)

  2. 調(diào)用 park 就是要看需不需要停下來(lái)歇息

  3. 如果備用干糧耗盡,那么鉆進(jìn)帳篷歇息

  4. 如果備用干糧充足缸剪,那么不需停留吗铐,繼續(xù)前進(jìn)

  5. 調(diào)用 unpark,就好比令干糧充足

  6. 因?yàn)楸嘲臻g有限杏节,多次調(diào)用 unpark 僅會(huì)補(bǔ)充一份備用干糧

  7. 如果這時(shí)線程還在帳篷唬渗,就喚醒讓他繼續(xù)前進(jìn)

  8. 如果這時(shí)線程還在運(yùn)行,那么下次他調(diào)用 park 時(shí)奋渔,僅是消耗掉備用干糧镊逝,不需停留繼續(xù)前進(jìn)

先調(diào)用park再調(diào)用upark的過(guò)程

1.先調(diào)用park

  1. 當(dāng)前線程調(diào)用 Unsafe.park() 方法
  2. 檢查 _counter ,本情況為 0嫉鲸,這時(shí)撑蒜,獲得 _mutex 互斥鎖(mutex對(duì)象有個(gè)等待隊(duì)列 _cond)
  3. 線程進(jìn)入 _cond 條件變量阻塞
  4. 設(shè)置 _counter = 0

<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1594531894163</figcaption>

2.調(diào)用upark

  1. 調(diào)用 Unsafe.unpark(Thread_0) 方法,設(shè)置 _counter 為 1
  2. 喚醒 _cond 條件變量中的 Thread_0
  3. Thread_0 恢復(fù)運(yùn)行
  4. 設(shè)置 _counter 為 0

<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1594532057205</figcaption>

先調(diào)用upark再調(diào)用park的過(guò)程

  1. 調(diào)用 Unsafe.unpark(Thread_0) 方法玄渗,設(shè)置 _counter 為 1
  2. 當(dāng)前線程調(diào)用 Unsafe.park() 方法
  3. 檢查 _counter 座菠,本情況為 1,這時(shí)線程無(wú)需阻塞藤树,繼續(xù)運(yùn)行
  4. 設(shè)置 _counter 為 0

<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1594532135616</figcaption>

線程狀態(tài)轉(zhuǎn)換

<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">img</figcaption>

①NEW = = > RUNNABLE
  • 調(diào)用線程的start方法時(shí) NEW = = > RUNNABLE
②RUNNABLE < = = > WAITING
  1. 線程用synchronized(obj)獲取了對(duì)象鎖后

  2. 競(jìng)爭(zhēng)鎖成功辈灼,t 線程從WAITING = = >RUNNABLE

  3. 競(jìng)爭(zhēng)鎖失敗,t 線程從WAITING = = > BLOCKED

  4. 調(diào)用obj.wait()方法時(shí)也榄,t 線程從RUNNABLE = = > WAITING

  5. 調(diào)用obj.notify()巡莹,obj.notifyAll(),t.interrupt()時(shí)

③RUNNABLE < = = > WAITING
  • 線程調(diào)用t.join()方法時(shí)(當(dāng)前線程方法內(nèi)在t線程對(duì)象的監(jiān)視器上等待) RUNNABLE = = > WAITING
  • t線程運(yùn)行結(jié)束或調(diào)用當(dāng)前線程的interrupt()方法時(shí) WAITING = = > RUNNABLE
④RUNNABLE < = = > WAITING
  1. 當(dāng)前線程調(diào)用 LockSupport.park() 方法會(huì)讓當(dāng)前線程從 RUNNABLE = = > WAITING
  2. 調(diào)用 LockSupport.unpark(目標(biāo)線程) 或調(diào)用了線程 的 interrupt() 甜紫,會(huì)讓目標(biāo)線程從 WAITING = = > RUNNABLE
⑤RUNNABLE < = = > TIMED_WAITING

t 線程用 synchronized(obj) 獲取了對(duì)象鎖后

  1. 調(diào)用 obj.wait(long n) 方法時(shí)降宅,t 線程從 RUNNABLE = = > TIMED_WAITING

  2. t 線程等待時(shí)間超過(guò)了 n 毫秒,或調(diào)用 obj.notify() 囚霸, obj.notifyAll() 腰根, t.interrupt() 時(shí)

  3. 競(jìng)爭(zhēng)鎖成功,t 線程從 TIMED_WAITING = = > RUNNABLE

  4. 競(jìng)爭(zhēng)鎖失敗拓型,t 線程從 TIMED_WAITING = = > BLOCKED

⑥RUNNABLE < = = > TIMED_WAITING
  1. 當(dāng)前線程調(diào)用 t.join(long n) 方法時(shí)额嘿,當(dāng)前線程從 RUNNABLE = = >TIMED_WAITING注意是當(dāng)前線程在t 線程對(duì)象的

    監(jiān)視器上等待

  2. 當(dāng)前線程等待時(shí)間超過(guò)了 n 毫秒,或t 線程運(yùn)行結(jié)束劣挫,或調(diào)用了當(dāng)前線程的 interrupt() 時(shí)册养,當(dāng)前線程從

    TIMED_WAITING = = > RUNNABLE

⑦RUNNABLE < = = > TIMED_WAITING
  1. 當(dāng)前線程調(diào)用 Thread.sleep(long n) ,當(dāng)前線程從 RUNNABLE = = > TIMED_WAITING
  2. 當(dāng)前線程等待時(shí)間超過(guò)了 n 毫秒或調(diào)用了線程的interrupt() 压固,當(dāng)前線程從 TIMED_WAITING = = > RUNNABLE
⑧RUNNABLE < = = > TIMED_WAITING
  1. 當(dāng)前線程調(diào)用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long

    millis) 時(shí)球拦,當(dāng)前線程從 RUNNABLE = = > TIMED_WAITING

  2. 調(diào)用 LockSupport.unpark(目標(biāo)線程) 或調(diào)用了線程 的 interrupt() ,或是等待超時(shí),會(huì)

    讓目標(biāo)線程從TIMED_WAITING = = > RUNNABLE

⑨RUNNABLE < = = > BLOCKED
  • t線程用synchronized(obj)獲取了對(duì)象鎖如果競(jìng)爭(zhēng)失敗坎炼,RUNNABLE = = > BLOCKED

  • 持obj鎖線程的同步代碼塊執(zhí)行完畢愧膀,會(huì)喚醒該對(duì)象上所有BLOCKED的線程重新競(jìng)爭(zhēng),如果其中t線程競(jìng)爭(zhēng)成功從BLOCKED = = > RUNNABLE谣光,其他失敗的線程仍然是BLOCKED

⑩RUNNABLE < = = > TIMED_WAITING

當(dāng)前線程所有代碼運(yùn)行完畢檩淋,進(jìn)入TIMED_WAITING

多把鎖【增加并發(fā)度】

只使用一把鎖的情況下,不相干的任務(wù)也會(huì)等待萄金,比如一個(gè)線程睡覺(jué)和一個(gè)線程學(xué)習(xí)

這種情況下狼钮,睡覺(jué)和學(xué)習(xí)不應(yīng)該只是一把鎖,應(yīng)該分開捡絮,兩者便可并發(fā)執(zhí)行熬芜。

但使用多把鎖既有好處,也有壞處

  • 優(yōu)點(diǎn)

    可以增加并發(fā)度福稳,即不相關(guān)的任務(wù)可以并行

  • 缺點(diǎn)

    會(huì)造成死鎖等問(wèn)題

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`@Slf4j
public class MultiLock {
private final Object studyLock = new Object(); //學(xué)習(xí)鎖
private final Object sleepLock = new Object(); //睡覺(jué)鎖

public void study() throws InterruptedException {
    synchronized (studyLock){
        log.debug(Thread.currentThread().getName()+"在學(xué)習(xí)涎拉!");
        Thread.sleep(2000);
        log.debug("學(xué)習(xí)結(jié)束!");
    }
}

public void sleep() throws InterruptedException {
    synchronized (sleepLock){
        log.debug(Thread.currentThread().getName()+"在睡覺(jué)的圆!");
        Thread.sleep(1000);
        log.debug("睡覺(jué)結(jié)束凉翻!");
    }
}

}
class Test{
public static void main(String[] args) {
MultiLock multiLock = new MultiLock();
new Thread(()->{
try {
multiLock.study();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
multiLock.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}` </pre>

活躍性

線程沒(méi)有按預(yù)期結(jié)束卦睹,執(zhí)行不下去的情況邑闺,歸類為【活躍性】問(wèn)題栅组,除了死鎖以外,還有活鎖和饑餓者兩種情況

死鎖

一個(gè)線程在占用一個(gè)鎖的同時(shí)需要獲取多把鎖,但這些鎖被其他線程占用梅掠,其他線程也需要該線程占用的鎖酌住,就會(huì)陷入死循環(huán),無(wú)限等待阎抒,造成死鎖問(wèn)題酪我,

  • t1線程需要獲取A對(duì)象鎖,接下來(lái)要獲取B對(duì)象鎖,但被t2線程占用
  • t2線程需要獲取B對(duì)象鎖,接下來(lái)要獲取A對(duì)象鎖且叁,但被t1線程占用
檢測(cè)方法
  1. 檢測(cè)死鎖可以使用 jconsole工具都哭;
  2. 使用 jps 定位進(jìn)程 id,再用 jstack 定位死鎖
哲學(xué)家就餐問(wèn)題

<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1594553609905</figcaption>

有五位哲學(xué)家逞带,圍坐在圓桌旁欺矫。他們只做兩件事,思考和吃飯展氓,思考一會(huì)吃口飯穆趴,吃完飯后接著思考。

吃飯時(shí)要用兩根筷子吃带饱,桌上共有 5 根筷子毡代,每位哲學(xué)家左右手邊各有一根筷子阅羹。如果筷子被身邊的人拿著勺疼,自己就得等待

代碼實(shí)現(xiàn)

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">`public class Main {
public static void main(String[] args) {
chopstick c1 = new chopstick("c1");
chopstick c2 = new chopstick("c2");
chopstick c3 = new chopstick("c3");
chopstick c4 = new chopstick("c4");
chopstick c5 = new chopstick("c5");
new philosopher("阿基米德",c1,c2).start();
new philosopher("蘇格拉底",c2,c3).start();
new philosopher("柏拉圖",c3,c4).start();
new philosopher("黑格爾",c4,c5).start();
new philosopher("亞里士多德",c5,c1).start();
}
}
class chopstick{
private String name;

public chopstick(String name) {
    this.name = name;
}

@Override
public String toString() {
    return "chopstick{" +
            "name='" + name + '\'' +
            '}';
}

}

@Slf4j
class philosopher extends Thread{
private chopstick left;
private chopstick right;

public philosopher(String name,chopstick left,chopstick right){
    super(name);
    this.left=left;
    this.right=right;
}

@Override
public void run() {
    while (true) {
        synchronized (left){
            synchronized (right){
                eat();
            }
        }
    }
}

private void eat()  {
    log.debug(Thread.currentThread().getName()+"開始吃飯了");
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

}` </pre>

當(dāng)每個(gè)哲學(xué)家即線程持有一根筷子時(shí)教寂,他們都在等待另一個(gè)線程釋放鎖,因此造成了死鎖执庐。

活鎖

出現(xiàn)在兩個(gè)線程互相改變對(duì)方的結(jié)束條件(二者條件互斥)酪耕,最后誰(shuí)也無(wú)法結(jié)束。

結(jié)束條件count<20, 初始 count =25

  • t1線程執(zhí)行count++操作
  • t2線程并行執(zhí)行 count-- 操作
解決辦法:

設(shè)置隨機(jī)的睡眠時(shí)間轨淌,避免類似加減這樣的操作一起執(zhí)行

饑餓

很多教程中把饑餓定義為迂烁,一個(gè)線程由于優(yōu)先級(jí)太低,始終得不到 CPU 調(diào)度執(zhí)行递鹉,也不能夠結(jié)束盟步,饑餓的情況不易演

示,講讀寫鎖時(shí)會(huì)涉及饑餓問(wèn)題躏结。

一個(gè)線程饑餓的例子:

先來(lái)看看使用順序加鎖的方式解決之前的死鎖問(wèn)題却盘,就是兩個(gè)線程對(duì)兩個(gè)不同的對(duì)象加鎖的時(shí)候都使用相同的順序進(jìn)行

加鎖。但是這樣又會(huì)產(chǎn)生饑餓問(wèn)題

<figcaption style="margin: 5px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; text-align: center; color: rgb(136, 136, 136); font-size: 12px;">1594558469826</figcaption>

順序加鎖的解決方案(會(huì)造成饑餓問(wèn)題,其中一個(gè)線程會(huì)一直執(zhí)行媳拴,其他線程執(zhí)行的機(jī)會(huì)大大減少)

ReentrantLock (JUC下的類)

相對(duì)于 synchronized 它具備如下特點(diǎn)

  1. 可中斷
  2. 可以設(shè)置超時(shí)時(shí)間
  3. 可以設(shè)置為公平鎖
  4. 支持多個(gè)條件變量黄橘,即對(duì)與不滿足條件的線程可以放到不同的集合中等待

與 synchronized 一樣,都支持可重入

基本語(yǔ)法

<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.54902) 0px 2px 10px;">// 獲取鎖 reentrantLock.lock(); try { // 臨界區(qū) } finally { // 釋放鎖 reentrantLock.unlock(); } </pre>

可重入

可重入是指同一個(gè)線程如果首次獲得了這把鎖屈溉,那么它就是這把鎖的擁有者塞关,因此有權(quán)利再次獲取這把鎖

如果是不可重入鎖,那么第二次獲得鎖時(shí)子巾,自己也會(huì)被鎖擋住

可打斷

鎖超時(shí)

使用鎖超時(shí)解決哲學(xué)家就餐死鎖問(wèn)題:

公平鎖

synchronized鎖中帆赢,在entrylist等待的鎖在競(jìng)爭(zhēng)時(shí)不是按照先到先得來(lái)獲取鎖的,所以說(shuō)synchronized鎖時(shí)不公平的线梗;ReentranLock鎖默認(rèn)是不公平的匿醒,但是可以通過(guò)設(shè)置實(shí)現(xiàn)公平鎖。本意是為了解決之前提到的饑餓問(wèn)題缠导,但是公平鎖一般沒(méi)有必要廉羔,會(huì)降低并發(fā)度,使用trylock也可以實(shí)現(xiàn)僻造。

條件變量(休息室)

synchronized 中也有條件變量憋他,就是我們講原理時(shí)那個(gè) waitSet 休息室,當(dāng)條件不滿足時(shí)進(jìn)入 waitSet 等待 ReentrantLock 的條件變量比 synchronized 強(qiáng)大之處在于髓削,它是支持多個(gè)條件變量的竹挡,這就好比

  1. synchronized 是那些不滿足條件的線程都在一間休息室等消息
  2. 而 ReentrantLock 支持多間休息室,有專門等煙的休息室立膛、專門等早餐的休息室揪罕、喚醒時(shí)也是按休息室來(lái)喚 醒

使用要點(diǎn):

  1. await 前需要獲得鎖
  2. await 執(zhí)行后梯码,會(huì)釋放鎖,進(jìn)入 conditionObject 等待
  3. await 的線程被喚醒(或打斷好啰、或超時(shí))取重新競(jìng)爭(zhēng) lock 鎖轩娶,執(zhí)行喚醒的線程爺必須先獲得鎖
  4. 競(jìng)爭(zhēng) lock 鎖成功后,從 await 后繼續(xù)執(zhí)行

同步模式之順序控制

  1. 固定運(yùn)行順序框往,比如鳄抒,必須先 2 后 1 打印

  2. wait notify 版

  3. Park Unpark 版

  4. Lock 條件變量版

  5. 交替輸出,線程 1 輸出 a 5 次椰弊,線程 2 輸出 b 5 次许溅,線程 3 輸出 c 5 次。現(xiàn)在要求輸出 abcabcabcabcabc 怎么實(shí)現(xiàn)

  6. wait notify 版

  7. Lock 條件變量版

  8. Park Unpark 版

本章小結(jié)

本章我們需要重點(diǎn)掌握的是

  1. 分析多線程訪問(wèn)共享資源時(shí)秉版,哪些代碼片段屬于臨界區(qū)

  2. 使用 synchronized 互斥解決臨界區(qū)的線程安全問(wèn)題

  3. 掌握 synchronized 鎖對(duì)象語(yǔ)法

  4. 掌握 synchronzied 加載成員方法和靜態(tài)方法語(yǔ)法

  5. 掌握 wait/notify 同步方法

  6. 使用 lock 互斥解決臨界區(qū)的線程安全問(wèn)題 掌握 lock 的使用細(xì)節(jié):可打斷贤重、鎖超時(shí)、公平鎖清焕、條件變量

  7. 學(xué)會(huì)分析變量的線程安全性并蝗、掌握常見(jiàn)線程安全類的使用

  8. 了解線程活躍性問(wèn)題:死鎖、活鎖耐朴、饑餓

  9. 應(yīng)用方面

  10. 互斥:使用 synchronized 或 Lock 達(dá)到共享資源互斥效果借卧,實(shí)現(xiàn)原子性效果,保證線程安全筛峭。

  11. 同步:使用 wait/notify 或 Lock 的條件變量來(lái)達(dá)到線程間通信效果铐刘。

  12. 原理方面

  13. monitor、synchronized 影晓、wait/notify 原理

  14. synchronized 進(jìn)階原理

  15. park & unpark 原理

  16. 模式方面

  17. 同步模式之保護(hù)性暫停

  18. 異步模式之生產(chǎn)者消費(fèi)者

  19. 同步模式之順序控制

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末镰吵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子挂签,更是在濱河造成了極大的恐慌疤祭,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饵婆,死亡現(xiàn)場(chǎng)離奇詭異勺馆,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)侨核,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門草穆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人搓译,你說(shuō)我怎么就攤上這事悲柱。” “怎么了些己?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵豌鸡,是天一觀的道長(zhǎng)嘿般。 經(jīng)常有香客問(wèn)我,道長(zhǎng)涯冠,這世上最難降的妖魔是什么炉奴? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮功偿,結(jié)果婚禮上盆佣,老公的妹妹穿的比我還像新娘往堡。我一直安慰自己械荷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布虑灰。 她就那樣靜靜地躺著吨瞎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪穆咐。 梳的紋絲不亂的頭發(fā)上颤诀,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音对湃,去河邊找鬼崖叫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拍柒,可吹牛的內(nèi)容都是我干的心傀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼拆讯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼脂男!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起种呐,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宰翅,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后爽室,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體汁讼,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年阔墩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嘿架。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡戈擒,死狀恐怖眶明,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情筐高,我是刑警寧澤搜囱,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布丑瞧,位于F島的核電站,受9級(jí)特大地震影響蜀肘,放射性物質(zhì)發(fā)生泄漏绊汹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一扮宠、第九天 我趴在偏房一處隱蔽的房頂上張望西乖。 院中可真熱鬧,春花似錦坛增、人聲如沸获雕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)届案。三九已至,卻和暖如春罢艾,著一層夾襖步出監(jiān)牢的瞬間楣颠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工咐蚯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留童漩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓春锋,卻偏偏與公主長(zhǎng)得像矫膨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子看疙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容