Android開發(fā)中synchronized的三種使用方式詳解
synchronized的三種使用方式
**1.修飾實(shí)例方法,**作用于當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖。
沒有問題的寫法:
public class AccountingSync implements Runnable{
//共享資源(臨界資源)
static int i=0;
/**
* synchronized 修飾實(shí)例方法
*/
public synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<1000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
AccountingSync instance=new AccountingSync();
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
/**
* 輸出結(jié)果:
* 2000000
*/
}
因?yàn)檫@段代碼中只輸入了一個(gè)AccountingSync實(shí)例。
下面是有問題的寫法:
public class AccountingSyncBad implements Runnable{
static int i=0;
public synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<1000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
//new新實(shí)例
Thread t1=new Thread(new AccountingSyncBad());
//new新實(shí)例
Thread t2=new Thread(new AccountingSyncBad());
t1.start();
t2.start();
//join含義:當(dāng)前線程A等待thread線程終止之后才能從thread.join()返回
t1.join();
t2.join();
System.out.println(i);
}
}
在上述代碼中出現(xiàn)了一個(gè)嚴(yán)重的錯(cuò)誤,雖然我們使用了sychronized修飾了increase方法,但new了兩個(gè)不同的實(shí)例對(duì)象,這就意味著會(huì)出現(xiàn)兩個(gè)不同的實(shí)例對(duì)象鎖,這樣的話ti和t2都會(huì)進(jìn)入各自的對(duì)象鎖,因此線程安全就得不到保證。想要解決這個(gè)問題,就需要將sychronized作用于靜態(tài)的increase方法。
2.修飾靜態(tài)方法,作用于當(dāng)前類對(duì)象加鎖,進(jìn)入同步代碼前要獲得當(dāng)前類對(duì)象的鎖。
當(dāng)sychronized使用靜態(tài)方法時(shí),對(duì)象鎖就會(huì)是當(dāng)前類的class對(duì)象鎖。因?yàn)殪o態(tài)成員不屬于任何一個(gè)實(shí)例對(duì)象,所以通過class對(duì)象鎖可以控制靜態(tài) 成員的并發(fā)操作。
如果一個(gè)線程A調(diào)用一個(gè)實(shí)例對(duì)象的非static synchronized方法,而線程B需要調(diào)用這個(gè)實(shí)例對(duì)象所屬類的靜態(tài) synchronized方法,是允許的,不會(huì)發(fā)生互斥現(xiàn)象,因?yàn)樵L問靜態(tài) synchronized 方法占用的鎖是當(dāng)前類的class對(duì)象,而訪問非靜態(tài) synchronized 方法占用的鎖是當(dāng)前實(shí)例對(duì)象鎖,看如下代碼:
public class AccountingSyncClass implements Runnable{
static int i=0;
/**
* 作用于靜態(tài)方法,鎖是當(dāng)前class對(duì)象,也就是
* AccountingSyncClass類對(duì)應(yīng)的class對(duì)象
*/
public static synchronized void increase(){
i++;
}
/**
* 非靜態(tài),訪問時(shí)鎖不一樣不會(huì)發(fā)生互斥
*/
public synchronized void increase4Obj(){
i++;
}
@Override
public void run() {
for(int j=0;j<1000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
//new新實(shí)例
Thread t1=new Thread(new AccountingSyncClass());
//new心事了
Thread t2=new Thread(new AccountingSyncClass());
//啟動(dòng)線程
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
這種情況下可能會(huì)發(fā)現(xiàn)線程安全問題(操作了共享靜態(tài)變量i)。
3.修飾代碼塊,指定加鎖對(duì)象,對(duì)給定對(duì)象加鎖,進(jìn)入同步代碼庫(kù)前要獲得給定對(duì)象的鎖。
synchronized同步代碼塊
public class AccountingSync implements Runnable{
static AccountingSync instance=new AccountingSync();
static int i=0;
@Override
public void run() {
//省略其他耗時(shí)操作....
//使用同步代碼塊對(duì)變量i進(jìn)行同步操作,鎖對(duì)象為instance
synchronized(instance){
for(int j=0;j<1000000;j++){
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
當(dāng)然除了instance作為對(duì)象外,我們還可以使用this對(duì)象(代表當(dāng)前實(shí)例)或者當(dāng)前類的class對(duì)象作為鎖,如下代碼:
//this,當(dāng)前實(shí)例對(duì)象鎖
synchronized(this){
for(int j=0;j<1000000;j++){
i++;
}
}
//class對(duì)象鎖
synchronized(AccountingSync.class){
for(int j=0;j<1000000;j++){
i++;
}
}
synchronized底層語(yǔ)義原理
Java 虛擬機(jī)中的同步(Synchronization)基于進(jìn)入和退出管程(Monitor)對(duì)象實(shí)現(xiàn), 無論是顯式同步(有明確的 monitorenter 和 monitorexit 指令,即同步代碼塊)還是隱式同步都是如此。
同步方法 并不是由 monitorenter 和 monitorexit 指令來實(shí)現(xiàn)同步的,而是由方法調(diào)用指令讀取運(yùn)行時(shí)常量池中方法的 ACC_SYNCHRONIZED 標(biāo)志來隱式實(shí)現(xiàn)的
理解Java對(duì)象頭與Monitor
在JVM中,對(duì)象在內(nèi)存中的布局分為三塊區(qū)域:對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充。

實(shí)例變量:存放類的屬性數(shù)據(jù)信息,包括父類的屬性信息,如果是數(shù)組的實(shí)例部分還包括數(shù)組的長(zhǎng)度,這部分內(nèi)存按4字節(jié)對(duì)齊。
填充數(shù)據(jù):由于虛擬機(jī)要求對(duì)象起始地址必須是8字節(jié)的整數(shù)倍。填充數(shù)據(jù)不是必須存在的,僅僅是為了字節(jié)對(duì)齊
頂部,則是Java頭對(duì)象,它實(shí)現(xiàn)synchronized的鎖對(duì)象的基礎(chǔ)。
synchronized使用的鎖對(duì)象是存儲(chǔ)在Java對(duì)象頭里的,jvm中采用2個(gè)字來存儲(chǔ)對(duì)象頭(如果對(duì)象是數(shù)組則會(huì)分配3個(gè)字,多出來的1個(gè)字記錄的是數(shù)組長(zhǎng)度),其主要結(jié)構(gòu)是由Mark Word 和 Class Metadata Address 組成,其結(jié)構(gòu)說明如下表:

其中Mark Word在默認(rèn)情況下存儲(chǔ)著對(duì)象的HashCode、分代年齡、鎖標(biāo)記位等以下是32位JVM的Mark Word默認(rèn)存儲(chǔ)結(jié)構(gòu)

由于對(duì)象頭的信息是與對(duì)象自身定義的數(shù)據(jù)沒有關(guān)系的額外存儲(chǔ)成本,因此考慮到JVM的空間效率,Mark Word 被設(shè)計(jì)成為一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu),以便存儲(chǔ)更多有效的數(shù)據(jù),它會(huì)根據(jù)對(duì)象本身的狀態(tài)復(fù)用自己的存儲(chǔ)空間,如32位JVM下,除了上述列出的Mark Word默認(rèn)存儲(chǔ)結(jié)構(gòu)外,還有如下可能變化的結(jié)構(gòu):

主要分析一下重量級(jí)鎖也就是通常說synchronized的對(duì)象鎖,鎖標(biāo)識(shí)位為10,其中指針指向的是monitor對(duì)象(也稱為管程或監(jiān)視器鎖)的起始地址。每個(gè)對(duì)象都存在著一個(gè) monitor 與之關(guān)聯(lián),對(duì)象與其 monitor 之間的關(guān)系有存在多種實(shí)現(xiàn)方式,如monitor可以與對(duì)象一起創(chuàng)建銷毀或當(dāng)線程試圖獲取對(duì)象鎖時(shí)自動(dòng)生成,但當(dāng)一個(gè) monitor 被某個(gè)線程持有后,它便處于鎖定狀態(tài)。
在Java虛擬機(jī)(HotSpot)中,monitor是由ObjectMonitor實(shí)現(xiàn)的,其主要數(shù)據(jù)結(jié)構(gòu)如下(位于HotSpot虛擬機(jī)源碼ObjectMonitor.hpp文件,C++實(shí)現(xiàn)的)。
ObjectMonitor() {
_header = NULL;
_count = 0; //記錄個(gè)數(shù)
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL; //處于wait狀態(tài)的線程,會(huì)被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //處于等待鎖block狀態(tài)的線程,會(huì)被加入到該列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
ObjectMonitor中有兩個(gè)隊(duì)列,_WaitSet 和 _EntryList,用來保存ObjectWaiter對(duì)象列表( 每個(gè)等待鎖的線程都會(huì)被封裝成ObjectWaiter對(duì)象),_owner指向持有ObjectMonitor對(duì)象的線程,當(dāng)多個(gè)線程同時(shí)訪問一段同步代碼時(shí),首先會(huì)進(jìn)入 _EntryList 集合,當(dāng)線程獲取到對(duì)象的monitor 后進(jìn)入 _Owner 區(qū)域并把monitor中的owner變量設(shè)置為當(dāng)前線程同時(shí)monitor中的計(jì)數(shù)器count加1,若線程調(diào)用 wait() 方法,將釋放當(dāng)前持有的monitor,owner變量恢復(fù)為null,count自減1,同時(shí)該線程進(jìn)入 WaitSet集合中等待被喚醒。若當(dāng)前線程執(zhí)行完畢也將釋放monitor(鎖)并復(fù)位變量的值,以便其他線程進(jìn)入獲取monitor(鎖)。

monitor對(duì)象存在于每個(gè)Java對(duì)象的對(duì)象頭中(存儲(chǔ)的指針的指向),synchronized鎖便是通過這種方式獲取鎖的,也是為什么Java中任意對(duì)象可以作為鎖的原因,同時(shí)也是notify/notifyAll/wait等方法存在于頂級(jí)對(duì)象Object中的原因。
synchronized代碼塊底層原理
現(xiàn)在我們重新定義一個(gè)synchronized修飾的同步代碼塊,在代碼塊中操作共享變量i,如下:
public class SyncCodeBlock {
public int i;
public void syncTask(){
//同步代碼庫(kù)
synchronized (this){
i++;
}
}
}
編譯上述代碼并使用javap反編譯后得到字節(jié)碼如下(這里我們省略一部分沒有必要的信息):
Classfile /Users/zejian/Downloads/Java8_Action/src/main/java/com/zejian/concurrencys/SyncCodeBlock.class
Last modified 2017-6-2; size 426 bytes
MD5 checksum c80bc322c87b312de760942820b4fed5
Compiled from "SyncCodeBlock.java"
public class com.zejian.concurrencys.SyncCodeBlock
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
//........省略常量池中數(shù)據(jù)
//構(gòu)造函數(shù)
public com.zejian.concurrencys.SyncCodeBlock();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
//===========主要看看syncTask方法實(shí)現(xiàn)================
public void syncTask();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter //注意此處,進(jìn)入同步方法
4: aload_0
5: dup
6: getfield #2 // Field i:I
9: iconst_1
10: iadd
11: putfield #2 // Field i:I
14: aload_1
15: monitorexit //注意此處,退出同步方法
16: goto 24
19: astore_2
20: aload_1
21: monitorexit //注意此處,退出同步方法
22: aload_2
23: athrow
24: return
Exception table:
//省略其他字節(jié)碼.......
}
SourceFile: "SyncCodeBlock.java"
我們主要關(guān)注字節(jié)碼中的如下代碼
3: monitorenter //進(jìn)入同步方法
//..........省略其他
15: monitorexit //退出同步方法
16: goto 24
//省略其他.......
21: monitorexit //退出同步方法
從字節(jié)碼中可知同步語(yǔ)句塊的實(shí)現(xiàn)使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代碼塊的開始位置,monitorexit指令則指明同步代碼塊的結(jié)束位置,當(dāng)執(zhí)行monitorenter指令時(shí),當(dāng)前線程將試圖獲取 objectref(即對(duì)象鎖) 所對(duì)應(yīng)的 monitor 的持有權(quán),當(dāng) objectref 的 monitor 的進(jìn)入計(jì)數(shù)器為 0,那線程可以成功取得 monitor,并將計(jì)數(shù)器值設(shè)置為 1,取鎖成功。**如果當(dāng)前線程已經(jīng)擁有 objectref 的 monitor 的持有權(quán),那它可以重入這個(gè) monitor (關(guān)于重入性稍后會(huì)分析),重入時(shí)計(jì)數(shù)器的值也會(huì)加 1。**倘若其他線程已經(jīng)擁有 objectref 的 monitor 的所有權(quán),那當(dāng)前線程將被阻塞,直到正在執(zhí)行線程執(zhí)行完畢,即monitorexit指令被執(zhí)行,執(zhí)行線程將釋放 monitor(鎖)并設(shè)置計(jì)數(shù)器值為0 ,其他線程將有機(jī)會(huì)持有 monitor 。值得注意的是編譯器將會(huì)確保無論方法通過何種方式完成,方法中調(diào)用過的每條 monitorenter 指令都有執(zhí)行其對(duì)應(yīng) monitorexit 指令,而無論這個(gè)方法是正常結(jié)束還是異常結(jié)束。為了保證在方法異常完成時(shí) monitorenter 和 monitorexit 指令依然可以正確配對(duì)執(zhí)行,編譯器會(huì)自動(dòng)產(chǎn)生一個(gè)異常處理器,這個(gè)異常處理器聲明可處理所有的異常,它的目的就是用來執(zhí)行 monitorexit 指令。從字節(jié)碼中也可以看出多了一個(gè)monitorexit指令,它就是異常結(jié)束時(shí)被執(zhí)行的釋放monitor 的指令。
synchronized方法底層原理
方法級(jí)的同步是隱式,即無需通過字節(jié)碼指令來控制的,它實(shí)現(xiàn)在方法調(diào)用和返回操作之中。JVM可以從方法常量池中的方法表結(jié)構(gòu)(method_info Structure) 中的 ACC_SYNCHRONIZED 訪問標(biāo)志區(qū)分一個(gè)方法是否同步方法。當(dāng)方法調(diào)用時(shí),調(diào)用指令將會(huì) 檢查方法的 ACC_SYNCHRONIZED 訪問標(biāo)志是否被設(shè)置,如果設(shè)置了,執(zhí)行線程將先持有monitor(虛擬機(jī)規(guī)范中用的是管程一詞), 然后再執(zhí)行方法,最后再方法完成(無論是正常完成還是非正常完成)時(shí)釋放monitor。在方法執(zhí)行期間,執(zhí)行線程持有了monitor,其他任何線程都無法再獲得同一個(gè)monitor。如果一個(gè)同步方法執(zhí)行期間拋 出了異常,并且在方法內(nèi)部無法處理此異常,那這個(gè)同步方法所持有的monitor將在異常拋到同步方法之外時(shí)自動(dòng)釋放。
我們看看字節(jié)碼層面如何實(shí)現(xiàn):
public class SyncMethod {
public int i;
public synchronized void syncTask(){
i++;
}
}
使用javap反編譯后的字節(jié)碼如下:
Classfile /Users/zejian/Downloads/Java8_Action/src/main/java/com/zejian/concurrencys/SyncMethod.class
Last modified 2017-6-2; size 308 bytes
MD5 checksum f34075a8c059ea65e4cc2fa610e0cd94
Compiled from "SyncMethod.java"
public class com.zejian.concurrencys.SyncMethod
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool;
//省略沒必要的字節(jié)碼
//==================syncTask方法======================
public synchronized void syncTask();
descriptor: ()V
//方法標(biāo)識(shí)ACC_PUBLIC代表public修飾,ACC_SYNCHRONIZED指明該方法為同步方法
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_1
6: iadd
7: putfield #2 // Field i:I
10: return
LineNumberTable:
line 12: 0
line 13: 10
}
SourceFile: "SyncMethod.java"
從字節(jié)碼中可以看出,synchronized修飾的方法并沒有monitorenter指令和monitorexit指令,取得代之的確實(shí)是ACC_SYNCHRONIZED標(biāo)識(shí),該標(biāo)識(shí)指明了該方法是一個(gè)同步方法,JVM通過該ACC_SYNCHRONIZED訪問標(biāo)志來辨別一個(gè)方法是否聲明為同步方法,從而執(zhí)行相應(yīng)的同步調(diào)用。操作系統(tǒng)實(shí)現(xiàn)線程之間的切換時(shí)需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài)
Java虛擬機(jī)對(duì)synchronized的優(yōu)化
鎖的狀態(tài)總共有四種,無鎖狀態(tài)、偏向鎖、輕量級(jí)鎖和重量級(jí)鎖。隨著鎖的競(jìng)爭(zhēng),鎖可以從偏向鎖升級(jí)到輕量級(jí)鎖,再升級(jí)的重量級(jí)鎖,但是鎖的升級(jí)是單向的,也就是說只能從低到高升級(jí),不會(huì)出現(xiàn)鎖的降級(jí)
偏向鎖 在大多數(shù)情況下,鎖不僅不存在多線程競(jìng)爭(zhēng),而且總是由同一線程多次獲得,因此為了減少同一線程獲取鎖(會(huì)涉及到一些CAS操作,耗時(shí))的代價(jià)而引入偏向鎖。偏向鎖的核心思想是,如果一個(gè)線程獲得了鎖,那么鎖就進(jìn)入偏向模式,此時(shí)Mark Word 的結(jié)構(gòu)也變?yōu)槠蜴i結(jié)構(gòu),當(dāng)這個(gè)線程再次請(qǐng)求鎖時(shí),無需再做任何同步操作,即獲取鎖的過程,這樣就省去了大量有關(guān)鎖申請(qǐng)的操作,從而也就提供程序的性能。
但是對(duì)于鎖競(jìng)爭(zhēng)比較激烈的場(chǎng)合,偏向鎖就失效了,偏向鎖失敗后,并不會(huì)立即膨脹為重量級(jí)鎖,而是先升級(jí)為輕量級(jí)鎖。
輕量級(jí)鎖 量級(jí)鎖能夠提升程序性能的依據(jù)是“對(duì)絕大部分的鎖,在整個(gè)同步周期內(nèi)都不存在競(jìng)爭(zhēng)”,注意這是經(jīng)驗(yàn)數(shù)據(jù)。需要了解的是,輕量級(jí)鎖所適應(yīng)的場(chǎng)景是線程交替執(zhí)行同步塊的場(chǎng)合,如果存在同一時(shí)間訪問同一鎖的場(chǎng)合,就會(huì)導(dǎo)致輕量級(jí)鎖膨脹為重量級(jí)鎖。
自旋鎖 虛擬機(jī)為了避免線程真實(shí)地在操作系統(tǒng)層面掛起,還會(huì)進(jìn)行一項(xiàng)稱為自旋鎖的優(yōu)化手段。這是基于在大多數(shù)情況下,線程持有鎖的時(shí)間都不會(huì)太長(zhǎng),如果直接掛起操作系統(tǒng)層面的線程可能會(huì)得不償失,畢竟操作系統(tǒng)實(shí)現(xiàn)線程之間的切換時(shí)需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個(gè)狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間,時(shí)間成本相對(duì)較高,因此自旋鎖會(huì)假設(shè)在不久將來,當(dāng)前的線程可以獲得鎖,因此虛擬機(jī)會(huì)讓當(dāng)前想要獲取鎖的線程做幾個(gè)空循環(huán)(這也是稱為自旋的原因),一般不會(huì)太久,可能是50個(gè)循環(huán)或100循環(huán),在經(jīng)過若干次循環(huán)后,如果得到鎖,就順利進(jìn)入臨界區(qū)。如果還不能獲得鎖,那就會(huì)將線程在操作系統(tǒng)層面掛起,這就是自旋鎖的優(yōu)化方式,這種方式確實(shí)也是可以提升效率的。最后沒辦法也就只能升級(jí)為重量級(jí)鎖了。
鎖消除 Java虛擬機(jī)在JIT編譯時(shí)(可以簡(jiǎn)單理解為當(dāng)某段代碼即將第一次被執(zhí)行時(shí)進(jìn)行編譯,又稱即時(shí)編譯),通過對(duì)運(yùn)行上下文的掃描,去除不可能存在共享資源競(jìng)爭(zhēng)的鎖,通過這種方式消除沒有必要的鎖,可以節(jié)省毫無意義的請(qǐng)求鎖時(shí)間,如下StringBuffer的append是一個(gè)同步方法,但是在add方法中的StringBuffer屬于一個(gè)局部變量,并且不會(huì)被其他線程所使用,因此StringBuffer不可能存在共享資源競(jìng)爭(zhēng)的情景,JVM會(huì)自動(dòng)將其鎖消除。
關(guān)于synchronized可能需要了解的關(guān)鍵點(diǎn)
一個(gè)線程調(diào)用synchronized方法的同時(shí)在其方法體內(nèi)部調(diào)用該對(duì)象另一個(gè)synchronized方法,也就是說一個(gè)線程得到一個(gè)對(duì)象鎖后再次請(qǐng)求該對(duì)象鎖,是允許的,這就是synchronized的可重入性。如下:
public class AccountingSync implements Runnable{
static AccountingSync instance=new AccountingSync();
static int i=0;
static int j=0;
@Override
public void run() {
for(int j=0;j<1000000;j++){
//this,當(dāng)前實(shí)例對(duì)象鎖
synchronized(this){
i++;
increase();//synchronized的可重入性
}
}
}
public synchronized void increase(){
j++;
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
在獲取當(dāng)前實(shí)例對(duì)象鎖后進(jìn)入synchronized代碼塊執(zhí)行同步代碼,并在代碼塊中調(diào)用了當(dāng)前實(shí)例對(duì)象的另外一個(gè)synchronized方法,再次請(qǐng)求當(dāng)前實(shí)例鎖時(shí),將被允許,進(jìn)而執(zhí)行方法體代碼,這就是重入鎖最直接的體現(xiàn),需要特別注意另外一種情況,當(dāng)子類繼承父類時(shí),子類也是可以通過可重入鎖調(diào)用父類的同步方法。注意由于synchronized是基于monitor實(shí)現(xiàn)的,因此每次重入,monitor中的計(jì)數(shù)器仍會(huì)加1。
線程中斷與synchronized
正如中斷二字所表達(dá)的意義,在線程運(yùn)行(run方法)中間打斷它,在Java中,提供了以下3個(gè)有關(guān)線程中斷的方法
//中斷線程(實(shí)例方法) public void Thread.interrupt(); //判斷線程是否被中斷(實(shí)例方法) public boolean Thread.isInterrupted(); //判斷是否被中斷并清除當(dāng)前中斷狀態(tài)(靜態(tài)方法) public static boolean Thread.interrupted();
當(dāng)一個(gè)線程處于被阻塞狀態(tài)或者試圖執(zhí)行一個(gè)阻塞操作時(shí),使用Thread.interrupt()方式中斷該線程,注意此時(shí)將會(huì)拋出一個(gè)InterruptedException的異常,同時(shí)中斷狀態(tài)將會(huì)被復(fù)位(由中斷狀態(tài)改為非中斷狀態(tài)),如下代碼將演示該過程:
public class InterruputSleepThread3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
//while在try中,通過異常中斷就可以退出run循環(huán)
try {
while (true) {
//當(dāng)前線程處于阻塞狀態(tài),異常必須捕捉處理,無法往外拋出
TimeUnit.SECONDS.sleep(2);
}
} catch (InterruptedException e) {
System.out.println("Interruted When Sleep");
boolean interrupt = this.isInterrupted();
//中斷狀態(tài)被復(fù)位
System.out.println("interrupt:"+interrupt);
}
}
};
t1.start();
TimeUnit.SECONDS.sleep(2);
//中斷處于阻塞狀態(tài)的線程
t1.interrupt();
/**
* 輸出結(jié)果:
Interruted When Sleep
interrupt:false
*/
}
}
除了阻塞中斷的情景,我們還可能會(huì)遇到處于運(yùn)行期且非阻塞的狀態(tài)的線程,這種情況下,直接調(diào)用Thread.interrupt()中斷線程是不會(huì)得到任響應(yīng)的,如下代碼,將無法中斷非阻塞狀態(tài)下的線程:
public class InterruputThread {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(){
@Override
public void run(){
while(true){
System.out.println("未被中斷");
}
}
};
t1.start();
TimeUnit.SECONDS.sleep(2);
t1.interrupt();
/**
* 輸出結(jié)果(無限執(zhí)行):
未被中斷
未被中斷
未被中斷
......
*/
}
}
雖然我們調(diào)用了interrupt方法,但線程t1并未被中斷,因?yàn)樘幱诜亲枞麪顟B(tài)的線程需要我們手動(dòng)進(jìn)行中斷檢測(cè)并結(jié)束程序,改進(jìn)后代碼如下:
public class InterruputThread {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(){
@Override
public void run(){
while(true){
//判斷當(dāng)前線程是否被中斷
if (this.isInterrupted()){
System.out.println("線程中斷");
break;
}
}
System.out.println("已跳出循環(huán),線程中斷!");
}
};
t1.start();
TimeUnit.SECONDS.sleep(2);
t1.interrupt();
/**
* 輸出結(jié)果:
線程中斷
已跳出循環(huán),線程中斷!
*/
}
}
簡(jiǎn)單總結(jié)一下中斷兩種情況,一種是當(dāng)線程處于阻塞狀態(tài)或者試圖執(zhí)行一個(gè)阻塞操作時(shí),我們可以使用實(shí)例方法interrupt()進(jìn)行線程中斷,執(zhí)行中斷操作后將會(huì)拋出interruptException異常(該異常必須捕捉無法向外拋出)并將中斷狀態(tài)復(fù)位,另外一種是當(dāng)線程處于運(yùn)行狀態(tài)時(shí),我們也可調(diào)用實(shí)例方法interrupt()進(jìn)行線程中斷,但同時(shí)必須手動(dòng)判斷中斷狀態(tài),并編寫中斷線程的代碼(其實(shí)就是結(jié)束run方法體的代碼)。有時(shí)我們?cè)诰幋a時(shí)可能需要兼顧以上兩種情況,那么就可以如下編寫:
public void run(){
try {
//判斷當(dāng)前線程是否已中斷,注意interrupted方法是靜態(tài)的,執(zhí)行后會(huì)對(duì)中斷狀態(tài)進(jìn)行復(fù)位
while (!Thread.interrupted()) {
TimeUnit.SECONDS.sleep(2);
}
} catch (InterruptedException e) {
}
}
中斷與synchronized
線程的中斷操作對(duì)于正在等待獲取的鎖對(duì)象的synchronized方法或者代碼塊并不起作用,也就是對(duì)于synchronized來說,如果一個(gè)線程在等待鎖,那么結(jié)果只有兩種,要么它獲得這把鎖繼續(xù)執(zhí)行,要么它就保存等待,即使調(diào)用中斷線程的方法,也不會(huì)生效。
/**
* Created by zejian on 2017/6/2.
* Blog : http://blog.csdn.net/javazejian [原文地址,請(qǐng)尊重原創(chuàng)]
*/
public class SynchronizedBlocked implements Runnable{
public synchronized void f() {
System.out.println("Trying to call f()");
while(true) // Never releases lock
Thread.yield();
}
/**
* 在構(gòu)造器中創(chuàng)建新線程并啟動(dòng)獲取對(duì)象鎖
*/
public SynchronizedBlocked() {
//該線程已持有當(dāng)前實(shí)例鎖
new Thread() {
public void run() {
f(); // Lock acquired by this thread
}
}.start();
}
public void run() {
//中斷判斷
while (true) {
if (Thread.interrupted()) {
System.out.println("中斷線程!!");
break;
} else {
f();
}
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedBlocked sync = new SynchronizedBlocked();
Thread t = new Thread(sync);
//啟動(dòng)后調(diào)用f()方法,無法獲取當(dāng)前實(shí)例鎖處于等待狀態(tài)
t.start();
TimeUnit.SECONDS.sleep(1);
//中斷線程,無法生效
t.interrupt();
}
}
我們?cè)赟ynchronizedBlocked構(gòu)造函數(shù)中創(chuàng)建一個(gè)新線程并啟動(dòng)獲取調(diào)用f()獲取到當(dāng)前實(shí)例鎖,由于SynchronizedBlocked自身也是線程,啟動(dòng)后在其run方法中也調(diào)用了f(),但由于對(duì)象鎖被其他線程占用,導(dǎo)致t線程只能等到鎖,此時(shí)我們調(diào)用了t.interrupt();但并不能中斷線程。
等待喚醒機(jī)制與synchronized
notify/notifyAll和wait方法,在使用這3個(gè)方法時(shí),必須處于synchronized代碼塊或者synchronized方法中,否則就會(huì)拋出IllegalMonitorStateException異常,這是因?yàn)檎{(diào)用這幾個(gè)方法前必須拿到當(dāng)前對(duì)象的監(jiān)視器monitor對(duì)象
synchronized (obj) {
obj.wait();
obj.notify();
obj.notifyAll();
}
與sleep方法不同的是wait方法調(diào)用完成后,線程將被暫停,**但wait方法將會(huì)釋放當(dāng)前持有的監(jiān)視器鎖(monitor),直到有線程調(diào)用notify/notifyAll方法后方能繼續(xù)執(zhí)行,而sleep方法只讓線程休眠并不釋放鎖。**同時(shí)notify/notifyAll方法調(diào)用后,并不會(huì)馬上釋放監(jiān)視器鎖,而是在相應(yīng)的synchronized(){}/synchronized方法執(zhí)行結(jié)束后才自動(dòng)釋放鎖。
到此這篇關(guān)于Android開發(fā)中synchronized的三種使用方式詳解的文章就介紹到這了,更多相關(guān)Android synchronized內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于android示例程序(bitmapfun) 高效加載圖片讓人無語(yǔ)地方
嘗試了使用git上的一個(gè)開源項(xiàng)目afinal(bitmapfun的封裝版)來加載圖片,但是在測(cè)試的時(shí)候發(fā)現(xiàn)了一個(gè)問題,新的圖片加載器(bitmapfun)比之前用的ImageDownloader要慢很多,特別是在網(wǎng)絡(luò)狀況不好的時(shí)候,那簡(jiǎn)直是太讓人無語(yǔ)了2013-04-04
Android系統(tǒng)進(jìn)程間通信(IPC)機(jī)制Binder中的Server啟動(dòng)過程源代碼分析
本文主要介紹Android IPC機(jī)制Binder中的Server啟動(dòng)過程源代碼,這里對(duì)Binder 中Server 啟動(dòng)過程中的源碼做了詳細(xì)的介紹,有研究Android源碼 Binder 通信的小伙伴可以參考下2016-08-08
Android實(shí)現(xiàn)動(dòng)態(tài)改變app圖標(biāo)的示例代碼
本篇文章主要介紹了Android實(shí)現(xiàn)動(dòng)態(tài)改變app圖標(biāo)的示例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-09-09
android使用ViewPager實(shí)現(xiàn)圖片自動(dòng)切換
這篇文章主要為大家詳細(xì)介紹了android使用ViewPager實(shí)現(xiàn)圖片自動(dòng)切換,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02
Kotlin語(yǔ)言中CompileSdkVersion與targetSdkVersion的區(qū)別淺析
這篇文章主要介紹了Kotlin語(yǔ)言中CompileSdkVersion和targetSdkVersion有什么區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-02-02
Android?換膚實(shí)現(xiàn)指南demo及案例解析
這篇文章主要為大家介紹了Android換膚指南demo及案例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
Android仿iOS側(cè)滑退出當(dāng)前界面功能
這篇文章主要為大家詳細(xì)介紹了Android仿iOS側(cè)滑退出當(dāng)前界面功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12

