線程問(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ū)
一個(gè)程序運(yùn)行多線程本身是沒(méi)有問(wèn)題的
問(wèn)題出現(xiàn)在多個(gè)線程共享資源的時(shí)候
多個(gè)線程同時(shí)對(duì)共享資源進(jìn)行讀操作本身也沒(méi)有問(wèn)題
問(wèn)題出現(xiàn)在對(duì)對(duì)共享資源同時(shí)進(jìn)行讀寫操作時(shí)就有問(wèn)題了
先定義一個(gè)叫做臨界區(qū)的概念:一段代碼內(nèi)如果存在對(duì)共享資源的
多線程讀寫操作
塑猖,那么稱這段代碼為臨界區(qū)
-
如
<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)致線程不安全。
- String
- Integer
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent 包下的類
不可變類的線程安全
String
和Integer
類都是不可變的類菩咨,因?yàn)槠漕悆?nèi)部狀態(tài)是不可改變的吠式,因此它們的方法都是線程安全的,有同
學(xué)或許有疑問(wèn)抽米,String
有 replace
特占,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>
每次指向到synchronized代碼塊時(shí),都會(huì)創(chuàng)建鎖記錄(Lock Record)對(duì)象抖甘,每個(gè)線程都會(huì)包括一個(gè)鎖記錄的結(jié)構(gòu)热鞍,鎖記錄內(nèi)部可以儲(chǔ)存對(duì)象的Mark Word和對(duì)象引用reference
-
圖片
<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>
讓鎖記錄中的Object reference指向?qū)ο螅⑶覈L試用cas(compare and sweep)替換Object對(duì)象的Mark Word 衔彻,將Mark Word 的值存入鎖記錄中
-
圖片
<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>
如果cas替換成功薇宠,那么對(duì)象的對(duì)象頭儲(chǔ)存的就是鎖記錄的地址和狀態(tài)01,如下所示
-
<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>
如果cas失敗艰额,有兩種情況
-
<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>
如果是其它線程已經(jīng)持有了該Object的輕量級(jí)鎖澄港,那么表示有競(jìng)爭(zhēng),將進(jìn)入鎖膨脹階段
如果是自己的線程已經(jīng)執(zhí)行了synchronized進(jìn)行加鎖柄沮,那么那么再添加一條 Lock Record 作為重入的計(jì)數(shù)
當(dāng)線程退出synchronized代碼塊的時(shí)候回梧,**如果獲取的是取值為 null 的鎖記錄 **废岂,表示有重入,這時(shí)重置鎖記錄狱意,表示重入計(jì)數(shù)減一
-
<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>
當(dāng)線程退出synchronized代碼塊的時(shí)候湖苞,如果獲取的鎖記錄取值不為 null,那么使用cas將Mark Word的值恢復(fù)給對(duì)象
成功則解鎖成功
失敗髓涯,則說(shuō)明輕量級(jí)鎖進(jìn)行了鎖膨脹或已經(jīng)升級(jí)為重量級(jí)鎖袒啼,進(jìn)入重量級(jí)鎖解鎖流程
鎖膨脹
如果在嘗試加輕量級(jí)鎖的過(guò)程中,cas操作無(wú)法成功纬纪,這是有一種情況就是其它線程已經(jīng)為這個(gè)對(duì)象加上了輕量級(jí)鎖蚓再,這是就要進(jìn)行鎖膨脹,將輕量級(jí)鎖變成重量級(jí)鎖包各。
當(dāng) Thread-1 進(jìn)行輕量級(jí)加鎖時(shí)摘仅,Thread-0 已經(jīng)對(duì)該對(duì)象加了輕量級(jí)鎖
-
<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>
這時(shí) Thread-1 加輕量級(jí)鎖失敗,進(jìn)入鎖膨脹流程
即為對(duì)象申請(qǐng)Monitor鎖问畅,讓Object指向重量級(jí)鎖地址娃属,然后自己進(jìn)入Monitor 的EntryList 變成BLOCKED狀態(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;">1583757586447</figcaption>
當(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)行上下文切換就獲得了鎖
自旋重試成功的情況
-
<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>
自旋重試失敗的情況,自旋了一定次數(shù)還是沒(mé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;">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ò)程
如果開啟了偏向鎖(默認(rèn)是開啟的)禾酱,那么對(duì)象剛創(chuàng)建之后微酬,Mark Word 最后三位的值101,并且這是它的Thread颤陶,epoch颗管,age都是0,在加鎖的時(shí)候進(jìn)行設(shè)置這些的值.
偏向鎖默認(rèn)是延遲的滓走,不會(huì)在程序啟動(dòng)的時(shí)候立刻生效垦江,如果想避免延遲,可以添加虛擬機(jī)參數(shù)來(lái)禁用延遲:-
XX:BiasedLockingStartupDelay=0
來(lái)禁用延遲注意:處于偏向鎖的對(duì)象解鎖后搅方,線程 ID(不能用Java方法獲取是操作系統(tǒng)提供的)仍存儲(chǔ)于對(duì)象頭中
加上虛擬機(jī)參數(shù)-XX:BiasedLockingStartupDelay=0進(jìn)行測(cè)試
輸出結(jié)果如下比吭,三次輸出的狀態(tài)碼都為101
-
<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
虛擬機(jī)參數(shù)
-XX:-UseBiasedLocking
-
輸出結(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值
-
測(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>
-
輸出結(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)只搁,我們使用wait
和notify
來(lái)輔助實(shí)現(xiàn)
-
代碼音比,虛擬機(jī)參數(shù)
-XX:BiasedLockingStartupDelay=0
確保我們的程序最開始使用了偏向鎖!
-
輸出結(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)
- sleep是Thread方法鸯檬,而是Object的方法
- sleep不需要強(qiáng)制和synchronized配合使用,但wait需要
- sleep在睡眠的同時(shí)不會(huì)釋放對(duì)象鎖螺垢,但wait在等待時(shí)候會(huì)釋放對(duì)象鎖
- 線程的狀態(tài)都是TIME_WAIT
同步模式之保護(hù)性暫停
即 Guarded Suspension喧务,用在一個(gè)線程等待另一個(gè)線程的執(zhí)行結(jié)果,要點(diǎn):
- 有一個(gè)結(jié)果需要從一個(gè)線程傳遞到另一個(gè)線程枉圃,讓他們關(guān)聯(lián)同一個(gè) GuardedObject
- 如果有結(jié)果不斷從一個(gè)線程到另一個(gè)線程那么可以使用消息隊(duì)列(見(jiàn)生產(chǎn)者/消費(fèi)者)
- JDK 中功茴,join 的實(shí)現(xiàn)、Future 的實(shí)現(xiàn)孽亲,采用的就是此模式
- 因?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)
- 與前面的保護(hù)性暫停中的 GuardObject 不同,不需要產(chǎn)生結(jié)果和消費(fèi)結(jié)果的線程一一對(duì)應(yīng)
- 消費(fèi)隊(duì)列可以用來(lái)平衡生產(chǎn)和消費(fèi)的線程資源
- 生產(chǎn)者僅負(fù)責(zé)產(chǎn)生結(jié)果數(shù)據(jù)箱舞,不關(guān)心數(shù)據(jù)該如何處理遍坟,而消費(fèi)者專心處理結(jié)果數(shù)據(jù)
- 消息隊(duì)列是有容量限制的,滿時(shí)不會(huì)再加入數(shù)據(jù)晴股,空時(shí)不會(huì)再消耗數(shù)據(jù)
- 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
打個(gè)比喻線程就像一個(gè)旅人,Parker 就像他隨身攜帶的背包涡真,條件變量 _ cond就好比背包中的帳篷分俯。_counter 就好比背包中的備用干糧(0 為耗盡肾筐,1 為充足)
調(diào)用 park 就是要看需不需要停下來(lái)歇息
如果備用干糧耗盡,那么鉆進(jìn)帳篷歇息
如果備用干糧充足缸剪,那么不需停留吗铐,繼續(xù)前進(jìn)
調(diào)用 unpark,就好比令干糧充足
因?yàn)楸嘲臻g有限杏节,多次調(diào)用 unpark 僅會(huì)補(bǔ)充一份備用干糧
如果這時(shí)線程還在帳篷唬渗,就喚醒讓他繼續(xù)前進(jìn)
如果這時(shí)線程還在運(yùn)行,那么下次他調(diào)用 park 時(shí)奋渔,僅是消耗掉備用干糧镊逝,不需停留繼續(xù)前進(jìn)
先調(diào)用park再調(diào)用upark的過(guò)程
1.先調(diào)用park
- 當(dāng)前線程調(diào)用 Unsafe.park() 方法
- 檢查 _counter ,本情況為 0嫉鲸,這時(shí)撑蒜,獲得 _mutex 互斥鎖(mutex對(duì)象有個(gè)等待隊(duì)列 _cond)
- 線程進(jìn)入 _cond 條件變量阻塞
- 設(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
- 調(diào)用 Unsafe.unpark(Thread_0) 方法,設(shè)置 _counter 為 1
- 喚醒 _cond 條件變量中的 Thread_0
- Thread_0 恢復(fù)運(yùn)行
- 設(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ò)程
- 調(diào)用 Unsafe.unpark(Thread_0) 方法玄渗,設(shè)置 _counter 為 1
- 當(dāng)前線程調(diào)用 Unsafe.park() 方法
- 檢查 _counter 座菠,本情況為 1,這時(shí)線程無(wú)需阻塞藤树,繼續(xù)運(yùn)行
- 設(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
線程用synchronized(obj)獲取了對(duì)象鎖后
競(jìng)爭(zhēng)鎖成功辈灼,t 線程從WAITING = = >RUNNABLE
競(jìng)爭(zhēng)鎖失敗,t 線程從WAITING = = > BLOCKED
調(diào)用obj.wait()方法時(shí)也榄,t 線程從RUNNABLE = = > WAITING
調(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
- 當(dāng)前線程調(diào)用 LockSupport.park() 方法會(huì)讓當(dāng)前線程從 RUNNABLE = = > WAITING
- 調(diào)用 LockSupport.unpark(目標(biāo)線程) 或調(diào)用了線程 的 interrupt() 甜紫,會(huì)讓目標(biāo)線程從 WAITING = = > RUNNABLE
⑤RUNNABLE < = = > TIMED_WAITING
t 線程用 synchronized(obj) 獲取了對(duì)象鎖后
調(diào)用 obj.wait(long n) 方法時(shí)降宅,t 線程從 RUNNABLE = = > TIMED_WAITING
t 線程等待時(shí)間超過(guò)了 n 毫秒,或調(diào)用 obj.notify() 囚霸, obj.notifyAll() 腰根, t.interrupt() 時(shí)
競(jìng)爭(zhēng)鎖成功,t 線程從 TIMED_WAITING = = > RUNNABLE
競(jìng)爭(zhēng)鎖失敗拓型,t 線程從 TIMED_WAITING = = > BLOCKED
⑥RUNNABLE < = = > TIMED_WAITING
-
當(dāng)前線程調(diào)用 t.join(long n) 方法時(shí)额嘿,當(dāng)前線程從 RUNNABLE = = >TIMED_WAITING注意是當(dāng)前線程在t 線程對(duì)象的
監(jiān)視器上等待
-
當(dāng)前線程等待時(shí)間超過(guò)了 n 毫秒,或t 線程運(yùn)行結(jié)束劣挫,或調(diào)用了當(dāng)前線程的 interrupt() 時(shí)册养,當(dāng)前線程從
TIMED_WAITING = = > RUNNABLE
⑦RUNNABLE < = = > TIMED_WAITING
- 當(dāng)前線程調(diào)用 Thread.sleep(long n) ,當(dāng)前線程從 RUNNABLE = = > TIMED_WAITING
- 當(dāng)前線程等待時(shí)間超過(guò)了 n 毫秒或調(diào)用了線程的interrupt() 压固,當(dāng)前線程從 TIMED_WAITING = = > RUNNABLE
⑧RUNNABLE < = = > TIMED_WAITING
-
當(dāng)前線程調(diào)用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long
millis) 時(shí)球拦,當(dāng)前線程從 RUNNABLE = = > TIMED_WAITING
-
調(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è)方法
- 檢測(cè)死鎖可以使用 jconsole工具都哭;
- 使用 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)
- 可中斷
- 可以設(shè)置超時(shí)時(shí)間
- 可以設(shè)置為公平鎖
- 支持多個(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è)條件變量的竹挡,這就好比
- synchronized 是那些不滿足條件的線程都在一間休息室等消息
- 而 ReentrantLock 支持多間休息室,有專門等煙的休息室立膛、專門等早餐的休息室揪罕、喚醒時(shí)也是按休息室來(lái)喚 醒
使用要點(diǎn):
- await 前需要獲得鎖
- await 執(zhí)行后梯码,會(huì)釋放鎖,進(jìn)入 conditionObject 等待
- await 的線程被喚醒(或打斷好啰、或超時(shí))取重新競(jìng)爭(zhēng) lock 鎖轩娶,執(zhí)行喚醒的線程爺必須先獲得鎖
- 競(jìng)爭(zhēng) lock 鎖成功后,從 await 后繼續(xù)執(zhí)行
同步模式之順序控制
固定運(yùn)行順序框往,比如鳄抒,必須先 2 后 1 打印
wait notify 版
Park Unpark 版
Lock 條件變量版
交替輸出,線程 1 輸出 a 5 次椰弊,線程 2 輸出 b 5 次许溅,線程 3 輸出 c 5 次。現(xiàn)在要求輸出 abcabcabcabcabc 怎么實(shí)現(xiàn)
wait notify 版
Lock 條件變量版
Park Unpark 版
本章小結(jié)
本章我們需要重點(diǎn)掌握的是
分析多線程訪問(wèn)共享資源時(shí)秉版,哪些代碼片段屬于臨界區(qū)
使用 synchronized 互斥解決臨界區(qū)的線程安全問(wèn)題
掌握 synchronized 鎖對(duì)象語(yǔ)法
掌握 synchronzied 加載成員方法和靜態(tài)方法語(yǔ)法
掌握 wait/notify 同步方法
使用 lock 互斥解決臨界區(qū)的線程安全問(wèn)題 掌握 lock 的使用細(xì)節(jié):可打斷贤重、鎖超時(shí)、公平鎖清焕、條件變量
學(xué)會(huì)分析變量的線程安全性并蝗、掌握常見(jiàn)線程安全類的使用
了解線程活躍性問(wèn)題:死鎖、活鎖耐朴、饑餓
應(yīng)用方面
互斥:使用 synchronized 或 Lock 達(dá)到共享資源互斥效果借卧,實(shí)現(xiàn)原子性效果,保證線程安全筛峭。
同步:使用 wait/notify 或 Lock 的條件變量來(lái)達(dá)到線程間通信效果铐刘。
原理方面
monitor、synchronized 影晓、wait/notify 原理
synchronized 進(jìn)階原理
park & unpark 原理
模式方面
同步模式之保護(hù)性暫停
異步模式之生產(chǎn)者消費(fèi)者
同步模式之順序控制