這篇博客主要有以下幾點(diǎn)
- 什么是線程,和進(jìn)程的區(qū)別
- 線程有哪幾種狀態(tài)
- 線程安全的理解
- volatile關(guān)鍵字芳来,是線程安全嗎?為什么猜拾?和synchronize 的區(qū)別
- 用過哪些鎖即舌?lock 和 synchronize 區(qū)別
- 手寫代碼,線程1打印“A”挎袜,線程2打印“B”顽聂,按順序輸出“ABABABA...”
1.什么是線程,和進(jìn)程的區(qū)別
要解釋線程,就必須明白什么是進(jìn)程盯仪。
什么是進(jìn)程呢紊搪?
進(jìn)程是指運(yùn)行中的應(yīng)用程序,每個(gè)進(jìn)程都有自己獨(dú)立的地址空間(內(nèi)存空間)磨总,比如用戶點(diǎn)擊桌面的IE瀏覽器嗦明,就啟動(dòng)了一個(gè)進(jìn)程,操作系統(tǒng)就會(huì)為該進(jìn)程分配獨(dú)立的地址空間蚪燕。當(dāng)用戶再次點(diǎn)擊左面的IE瀏覽器娶牌,又啟動(dòng)了一個(gè)進(jìn)程,操作系統(tǒng)將為新的進(jìn)程分配新的獨(dú)立的地址空間馆纳。目前操作系統(tǒng)都支持多進(jìn)程诗良。
要點(diǎn):用戶每啟動(dòng)一個(gè)進(jìn)程,操作系統(tǒng)就會(huì)為該進(jìn)程分配一個(gè)獨(dú)立的內(nèi)存空間鲁驶。
在明白進(jìn)程后鉴裹,就比較容易理解線程的概念。
什么是線程呢钥弯?
是進(jìn)程中的一個(gè)實(shí)體径荔,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源脆霎,只擁有一點(diǎn)在運(yùn)行中必不可少的資源总处,但它可與同屬一個(gè)進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。一個(gè)線程可以創(chuàng)建和撤消另一個(gè)線程睛蛛,同一進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行鹦马。線程有就緒胧谈、阻塞和運(yùn)行三種基本狀態(tài)。
線程
1荸频、線程是輕量級的進(jìn)程
2菱肖、線程沒有獨(dú)立的地址空間(內(nèi)存空間)
3、線程是由進(jìn)程創(chuàng)建的(寄生在進(jìn)程)
4旭从、一個(gè)進(jìn)程可以擁有多個(gè)線程-->這就是我們常說的多線程編程
線程和進(jìn)程的區(qū)別
進(jìn)程和線程的主要差別在于它們是不同的操作系統(tǒng)資源管理方式稳强。進(jìn)程有獨(dú)立的地址空間,一個(gè)進(jìn)程崩潰后遇绞,在保護(hù)模式下不會(huì)對其它進(jìn)程產(chǎn)生影響键袱,而線程只是一個(gè)進(jìn)程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量摹闽,但線程之間沒有單獨(dú)的地址空間,一個(gè)線程死掉就等于整個(gè)進(jìn)程死掉褐健,所以多進(jìn)程的程序要比多線程的程序健壯付鹿,但在進(jìn)程切換時(shí),耗費(fèi)資源較大蚜迅,效率要差一些舵匾。但對于一些要求同時(shí)進(jìn)行并且又要共享某些變量的并發(fā)操作,只能用線程谁不,不能用進(jìn)程坐梯。
(1)進(jìn)程是資源的分配和調(diào)度的一個(gè)獨(dú)立單元,而線程是CPU調(diào)度的基本單元
(2)同一個(gè)進(jìn)程中可以包括多個(gè)線程刹帕,并且線程共享整個(gè)進(jìn)程的資源(寄存器吵血、堆棧、上下文)偷溺,一個(gè)進(jìn)行至少包括一個(gè)線程蹋辅。
(3)進(jìn)程的創(chuàng)建調(diào)用fork或者vfork,而線程的創(chuàng)建調(diào)用pthread_create挫掏,進(jìn)程結(jié)束后它擁有的所有線程都將銷毀侦另,而線程的結(jié)束不會(huì)影響同個(gè)進(jìn)程中的其他線程的結(jié)束
(4)線程是輕兩級的進(jìn)程,它的創(chuàng)建和銷毀所需要的時(shí)間比進(jìn)程小很多尉共,所有操作系統(tǒng)中的執(zhí)行功能都是創(chuàng)建線程去完成的
(5)線程中執(zhí)行時(shí)一般都要進(jìn)行同步和互斥褒傅,因?yàn)樗麄児蚕硗贿M(jìn)程的所有資源
(6)線程有自己的私有屬性TCB,線程id袄友,寄存器殿托、硬件上下文,而進(jìn)程也有自己的私有屬性進(jìn)程控制塊PCB杠河,這些私有屬性是不被共享的碌尔,用來標(biāo)示一個(gè)進(jìn)程或一個(gè)線程的標(biāo)志
線程有哪幾種狀態(tài)
在java中浇辜,線程通常有五種狀態(tài),創(chuàng)建唾戚,就緒柳洋,運(yùn)行、阻塞和死亡狀態(tài)叹坦。
第一是新建狀態(tài)熊镣。在生成線程對象,并沒有調(diào)用該對象的start方法募书,這是線程處于創(chuàng)建狀態(tài)绪囱。
第二是就緒狀態(tài)。當(dāng)調(diào)用了線程對象的start方法之后莹捡,該線程就進(jìn)入了就緒狀態(tài)鬼吵,但是此時(shí)線程調(diào)度程序還沒有把該線程設(shè)置為當(dāng)前線程,此時(shí)處于就緒狀態(tài)篮赢。在線程運(yùn)行之后齿椅,從等待或者睡眠中回來之后,也會(huì)處于就緒狀態(tài)启泣。
第三是運(yùn)行狀態(tài)涣脚。線程調(diào)度程序?qū)⑻幱诰途w狀態(tài)的線程設(shè)置為當(dāng)前線程,此時(shí)線程就進(jìn)入了運(yùn)行狀態(tài)寥茫,開始運(yùn)行run函數(shù)當(dāng)中的代碼遣蚀。
第四是阻塞狀態(tài)。線程正在運(yùn)行的時(shí)候纱耻,被暫停芭梯,通常是為了等待某個(gè)時(shí)間的發(fā)生(比如說某項(xiàng)資源就緒)之后再繼續(xù)運(yùn)行。sleep,suspend膝迎,wait等方法都可以導(dǎo)致線程阻塞粥帚。
第五是死亡狀態(tài)。如果一個(gè)線程的run方法執(zhí)行結(jié)束或者調(diào)用stop方法后限次,該線程就會(huì)死亡芒涡。對于已經(jīng)死亡的線程,無法再使用start方法令其進(jìn)入就緒卖漫。
離開運(yùn)行狀態(tài)3個(gè)方法小結(jié)
- 調(diào)用Thread.sleep():使當(dāng)前線程睡眠至少多少毫秒(盡管它可能在指定的時(shí)間之前被中斷)费尽。
- 調(diào)用Thread.yield():不能保障太多事情,盡管通常它會(huì)讓當(dāng)前運(yùn)行線程回到可運(yùn)行性狀態(tài)羊始,使得有相同優(yōu)先級的線程有機(jī)會(huì)執(zhí)行旱幼。
- 調(diào)用join()方法:保證當(dāng)前線程停止執(zhí)行,直到該線程所加入的線程完成為止突委。然而柏卤,如果它加入的線程沒有存活冬三,則當(dāng)前線程不需要停止。
線程安全的理解
- 以下來自百科
如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行缘缚,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼勾笆。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的桥滨,就是線程安全的窝爪。
或者說:一個(gè)類或者程序所提供的接口對于線程來說是原子操作或者多個(gè)線程之間的切換不會(huì)導(dǎo)致該接口的執(zhí)行結(jié)果存在二義性,也就是說我們不用考慮同步的問題。
線程安全問題都是由全局變量及靜態(tài)變量引起的齐媒。
若每個(gè)線程中對全局變量蒲每、靜態(tài)變量只有讀操作,而無寫操作喻括,一般來說邀杏,這個(gè)全局變量是線程安全的;若有多個(gè)線程同時(shí)執(zhí)行寫操作双妨,一般都需要考慮線程同步淮阐,否則的話就可能影響線程安全。
總結(jié)
線程安全
就是多線程訪問時(shí)刁品,采用了加鎖機(jī)制,當(dāng)一個(gè)線程訪問該類的某個(gè)數(shù)據(jù)時(shí)浩姥,進(jìn)行保護(hù)挑随,其他線程不能進(jìn)行訪問直到該線程讀取完,其他線程才可使用勒叠。不會(huì)出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)污染兜挨。
線程不安全
就是不提供數(shù)據(jù)訪問保護(hù),有可能出現(xiàn)多個(gè)線程先后更改數(shù)據(jù)造成所得到的數(shù)據(jù)是臟數(shù)據(jù)
volatile關(guān)鍵字眯分,是線程安全嗎拌汇?為什么?和synchronize 的區(qū)別
volatile關(guān)鍵字:
Java并發(fā)編程:volatile關(guān)鍵字解析
首先明確一點(diǎn):
volatile并不能保證線程安全性主要涉及到原子性弊决、可見性噪舀、有序性。
volatile關(guān)鍵字的兩層語義
一旦一個(gè)共享變量(類的成員變量飘诗、類的靜態(tài)成員變量)被volatile修飾之后与倡,那么就具備了兩層語義:
- 保證了不同線程對這個(gè)變量進(jìn)行操作時(shí)的可見性,即一個(gè)線程修改了某個(gè)變量的值昆稿,這新值對其他線程來說是立即可見的纺座。
- 禁止進(jìn)行指令重排序。
先看一段代碼溉潭,假如線程1先執(zhí)行净响,線程2后執(zhí)行:
//線程1
boolean stop = false;
while(!stop){
doSomething();
}
//線程2
stop = true;
這段代碼是很典型的一段代碼少欺,很多人在中斷線程時(shí)可能都會(huì)采用這種標(biāo)記辦法。但是事實(shí)上馋贤,這段代碼會(huì)完全運(yùn)行正確么赞别?即一定會(huì)將線程中斷么?不一定掸掸,也許在大多數(shù)時(shí)候氯庆,這個(gè)代碼能夠把線程中斷,但是也有可能會(huì)導(dǎo)致無法中斷線程(雖然這個(gè)可能性很小扰付,但是只要一旦發(fā)生這種情況就會(huì)造成死循環(huán)了)堤撵。
下面解釋一下這段代碼為何有可能導(dǎo)致無法中斷線程。在前面已經(jīng)解釋過羽莺,每個(gè)線程在運(yùn)行過程中都有自己的工作內(nèi)存实昨,那么線程1在運(yùn)行的時(shí)候,會(huì)將stop變量的值拷貝一份放在自己的工作內(nèi)存當(dāng)中盐固。
那么當(dāng)線程2更改了stop變量的值之后荒给,但是還沒來得及寫入主存當(dāng)中,線程2轉(zhuǎn)去做其他事情了刁卜,那么線程1由于不知道線程2對stop變量的更改志电,因此還會(huì)一直循環(huán)下去。
但是用volatile修飾之后就變得不一樣了:
第一:使用volatile關(guān)鍵字會(huì)強(qiáng)制將修改的值立即寫入主存蛔趴;
第二:使用volatile關(guān)鍵字的話挑辆,當(dāng)線程2進(jìn)行修改時(shí),會(huì)導(dǎo)致線程1的工作內(nèi)存中緩存變量stop的緩存行無效(反映到硬件層的話孝情,就是CPU的L1或者L2緩存中對應(yīng)的緩存行無效)鱼蝉;
第三:由于線程1的工作內(nèi)存中緩存變量stop的緩存行無效,所以線程1再次讀取變量stop的值時(shí)會(huì)去主存讀取箫荡。
那么在線程2修改stop值時(shí)(當(dāng)然這里包括2個(gè)操作魁亦,修改線程2工作內(nèi)存中的值,然后將修改后的值寫入內(nèi)存)羔挡,會(huì)使得線程1的工作內(nèi)存中緩存變量stop的緩存行無效洁奈,然后線程1讀取時(shí),發(fā)現(xiàn)自己的緩存行無效婉弹,它會(huì)等待緩存行對應(yīng)的主存地址被更新之后睬魂,然后去對應(yīng)的主存讀取最新的值。
那么線程1讀取到的就是最新的正確的值镀赌。
volatile保證原子性嗎氯哮?
從上面知道volatile關(guān)鍵字保證了操作的可見性,但是volatile能保證對變量的操作是原子性嗎?
下面看一個(gè)例子:
public class Test {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保證前面的線程都執(zhí)行完
Thread.yield();
System.out.println(test.inc);
}
}
大家想一下這段程序的輸出結(jié)果是多少喉钢?也許有些朋友認(rèn)為是10000姆打。但是事實(shí)上運(yùn)行它會(huì)發(fā)現(xiàn)每次運(yùn)行結(jié)果都不一致,都是一個(gè)小于10000的數(shù)字肠虽。
可能有的朋友就會(huì)有疑問幔戏,不對啊,上面是對變量inc進(jìn)行自增操作税课,由于volatile保證了可見性闲延,那么在每個(gè)線程中對inc自增完之后,在其他線程中都能看到修改后的值啊韩玩,所以有10個(gè)線程分別進(jìn)行了1000次操作垒玲,那么最終inc的值應(yīng)該是1000*10=10000。
這里面就有一個(gè)誤區(qū)了找颓,volatile關(guān)鍵字能保證可見性沒有錯(cuò)合愈,但是上面的程序錯(cuò)在沒能保證原子性』魇ǎ可見性只能保證每次讀取的是最新的值佛析,但是volatile沒辦法保證對變量的操作的原子性。
在前面已經(jīng)提到過彪蓬,自增操作是不具備原子性的寸莫,它包括讀取變量的原始值、進(jìn)行加1操作档冬、寫入工作內(nèi)存储狭。那么就是說自增操作的三個(gè)子操作可能會(huì)分割開執(zhí)行,就有可能導(dǎo)致下面這種情況出現(xiàn):
假如某個(gè)時(shí)刻變量inc的值為10捣郊,
線程1對變量進(jìn)行自增操作,線程1先讀取了變量inc的原始值慈参,然后線程1被阻塞了呛牲;
然后線程2對變量進(jìn)行自增操作,線程2也去讀取變量inc的原始值驮配,由于線程1只是對變量inc進(jìn)行讀取操作娘扩,而沒有對變量進(jìn)行修改操作,所以不會(huì)導(dǎo)致線程2的工作內(nèi)存中緩存變量inc的緩存行無效壮锻,所以線程2會(huì)直接去主存讀取inc的值琐旁,發(fā)現(xiàn)inc的值時(shí)10,然后進(jìn)行加1操作猜绣,并把11寫入工作內(nèi)存灰殴,最后寫入主存。
然后線程1接著進(jìn)行加1操作掰邢,由于已經(jīng)讀取了inc的值牺陶,注意此時(shí)在線程1的工作內(nèi)存中inc的值仍然為10伟阔,所以線程1對inc進(jìn)行加1操作后inc的值為11,然后將11寫入工作內(nèi)存掰伸,最后寫入主存皱炉。
那么兩個(gè)線程分別進(jìn)行了一次自增操作后,inc只增加了1狮鸭。
解釋到這里合搅,可能有朋友會(huì)有疑問,不對啊歧蕉,前面不是保證一個(gè)變量在修改volatile變量時(shí)灾部,會(huì)讓緩存行無效嗎?然后其他線程去讀就會(huì)讀到新的值廊谓,對梳猪,這個(gè)沒錯(cuò)。這個(gè)就是上面的happens-before規(guī)則中的volatile變量規(guī)則蒸痹,但是要注意春弥,線程1對變量進(jìn)行讀取操作之后,被阻塞了的話叠荠,并沒有對inc值進(jìn)行修改悔常。然后雖然volatile能保證線程2對變量inc的值讀取是從內(nèi)存中讀取的,但是線程1沒有進(jìn)行修改撕予,所以線程2根本就不會(huì)看到修改的值兢卵。
根源就在這里,自增操作不是原子性操作者娱,而且volatile也無法保證對變量的任何操作都是原子性的抡笼。
volatile的原理和實(shí)現(xiàn)機(jī)制
前面講述了源于volatile關(guān)鍵字的一些使用,下面我們來探討一下volatile到底如何保證可見性和禁止指令重排序的黄鳍。
下面這段話摘自《深入理解Java虛擬機(jī)》:
“觀察加入volatile關(guān)鍵字和沒有加入volatile關(guān)鍵字時(shí)所生成的匯編代碼發(fā)現(xiàn)推姻,加入volatile關(guān)鍵字時(shí),會(huì)多出一個(gè)lock前綴指令”
lock前綴指令實(shí)際上相當(dāng)于一個(gè)內(nèi)存屏障(也成內(nèi)存柵欄)框沟,內(nèi)存屏障會(huì)提供3個(gè)功能:
1)它確保指令重排序時(shí)不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置藏古,也不會(huì)把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時(shí)忍燥,在它前面的操作已經(jīng)全部完成拧晕;
2)它會(huì)強(qiáng)制將對緩存的修改操作立即寫入主存;
3)如果是寫操作梅垄,它會(huì)導(dǎo)致其他CPU中對應(yīng)的緩存行無效厂捞。
用過哪些鎖?lock 和 synchronize 區(qū)別
參考博客:
(轉(zhuǎn))Lock和synchronized比較詳解
Java線程并發(fā)中常見的鎖
java多線程對象鎖、類鎖蔫敲、同步機(jī)制詳解
Java提供了多種多線程鎖機(jī)制的實(shí)現(xiàn)方式饲嗽,常見的有synchronized、ReentrantLock奈嘿、Semaphore貌虾、AtomicInteger等。每種機(jī)制都有優(yōu)缺點(diǎn)與各自的適用場景裙犹,必須熟練掌握他們的特點(diǎn)才能在Java多線程應(yīng)用開發(fā)時(shí)得心應(yīng)手尽狠。——《Java鎖機(jī)制詳解》叶圃。
常見的鎖
Synchronized和Lock
其實(shí)我們真正用到的鎖也就那么兩三種袄膏,只不過依據(jù)設(shè)計(jì)方案和性質(zhì)對其進(jìn)行了大量的劃分。
以下一個(gè)鎖是原生語義上的實(shí)現(xiàn)
- Synchronized掺冠,它就是一個(gè):非公平沉馆,悲觀,獨(dú)享德崭,互斥斥黑,可重入的重量級鎖
以下兩個(gè)鎖都在JUC包下,是API層面上的實(shí)現(xiàn)
- ReentrantLock眉厨,它是一個(gè):默認(rèn)非公平但可實(shí)現(xiàn)公平的锌奴,悲觀,獨(dú)享憾股,互斥鹿蜀,可重入,重量級鎖服球。
- ReentrantReadWriteLocK茴恰,它是一個(gè),默認(rèn)非公平但可實(shí)現(xiàn)公平的斩熊,悲觀琐簇,寫?yīng)毾恚x共享座享,讀寫,可重入似忧,重量級鎖渣叛。
ReentrantLock與synchronized 的區(qū)別
本段內(nèi)容引自http://houlinyan.iteye.com/blog/1112535
ReentrantLock的高級操作
中斷等待
ReentrantLock 擁有Synchronized相同的并發(fā)性和內(nèi)存語義,此外還多了 鎖投票盯捌,定時(shí)鎖等候和中斷鎖等候淳衙。
線程A和B都要獲取對象O的鎖定,假設(shè)A獲取了對象O鎖,B將等待A釋放對O的鎖定
- 如果使用 synchronized 箫攀,如果A不釋放肠牲,B將一直等下去,不能被中斷
- 如果 使用ReentrantLock靴跛,如果A不釋放缀雳,可以使B在等待了足夠長的時(shí)間以后,中斷等待梢睛,而干別的事情
ReentrantLock獲取鎖定有三種方式:
lock(), 如果獲取了鎖立即返回肥印,如果別的線程持有鎖,當(dāng)前線程則一直處于休眠狀態(tài)绝葡,直到獲取鎖
tryLock(), 如果獲取了鎖立即返回true深碱,如果別的線程正持有鎖,立即返回false
tryLock(long timeout,TimeUnit unit)藏畅, 如果獲取了鎖定立即返回true敷硅,如果別的線程正持有鎖,會(huì)等待參數(shù)給定的時(shí)間愉阎,在等待的過程中绞蹦,如果獲取了鎖定,就返回true诫硕,如果等待超時(shí)坦辟,返回false;
lockInterruptibly:如果獲取了鎖定立即返回章办,如果沒有獲取鎖定锉走,當(dāng)前線程處于休眠狀態(tài),直到獲取鎖定藕届,或者當(dāng)前線程被別的線程中斷
可實(shí)現(xiàn)公平鎖
對于Java ReentrantLock而言挪蹭,通過構(gòu)造函數(shù)指定該鎖是否是公平鎖,默認(rèn)是非公平鎖休偶。非公平鎖的優(yōu)點(diǎn)在于吞吐量比公平鎖大梁厉。
鎖綁定多個(gè)條件
鎖綁定多個(gè)條件是指一個(gè)ReentrantLock對象可以同時(shí)綁定多個(gè)Condition對象,而在synchronized中踏兜,鎖對象的wait()和notify()或notifyAll()方法可以實(shí)現(xiàn)一個(gè)隱含的條件词顾,如果要和多于一個(gè)的條件關(guān)聯(lián)的時(shí)候,就不得不額外地添加一個(gè)鎖碱妆,而ReentrantLock則無須這樣做肉盹,只需要多次調(diào)用newCondition()方法即可。
synchronized的優(yōu)勢
synchronized是在JVM層面上實(shí)現(xiàn)的疹尾,不但可以通過一些監(jiān)控工具監(jiān)控synchronized的鎖定上忍,而且在代碼執(zhí)行時(shí)出現(xiàn)異常骤肛,JVM會(huì)自動(dòng)釋放鎖定,但是使用Lock則不行窍蓝,lock是通過代碼實(shí)現(xiàn)的腋颠,要保證鎖定一定會(huì)被釋放,就必須將unLock()放到finally{}中
應(yīng)用場景
在資源競爭不是很激烈的情況下吓笙,Synchronized的性能要優(yōu)于ReetrantLock淑玫,但是在資源競爭很激烈的情況下,Synchronized的性能會(huì)下降幾十倍观蓄,但是ReetrantLock的性能能維持常態(tài)混移;
其實(shí)如果按照名稱來說,鎖大概有以下名詞:
自旋鎖 侮穿,自旋鎖的其他種類歌径,阻塞鎖,可重入鎖 亲茅,讀寫鎖 回铛,互斥鎖 ,悲觀鎖 克锣,樂觀鎖 茵肃,公平鎖 ,偏向鎖袭祟, 對象鎖验残,線程鎖,鎖粗化巾乳, 鎖消除您没,輕量級鎖,重量級鎖胆绊, 信號量氨鹏,獨(dú)享鎖,共享鎖压状,分段鎖
按照其性質(zhì)分類
公平鎖/非公平鎖
公平鎖是指多個(gè)線程按照申請鎖的順序來獲取鎖仆抵。非公平鎖是指多個(gè)線程獲取鎖的順序并不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優(yōu)先獲取鎖种冬。有可能镣丑,會(huì)造成優(yōu)先級反轉(zhuǎn)或者饑餓現(xiàn)象。對于Java ReentrantLock而言娱两,通過構(gòu)造函數(shù)指定該鎖是否是公平鎖传轰,默認(rèn)是非公平鎖。非公平鎖的優(yōu)點(diǎn)在于吞吐量比公平鎖大谷婆。對于Synchronized而言慨蛙,也是一種非公平鎖。由于其并不像ReentrantLock是通過AQS的來實(shí)現(xiàn)線程調(diào)度纪挎,所以并沒有任何辦法使其變成公平鎖期贫。
樂觀鎖/悲觀鎖
樂觀鎖與悲觀鎖不是指具體的什么類型的鎖,而是指看待并發(fā)同步的角度异袄。悲觀鎖認(rèn)為對于同一個(gè)數(shù)據(jù)的并發(fā)操作通砍,一定是會(huì)發(fā)生修改的,哪怕沒有修改烤蜕,也會(huì)認(rèn)為修改封孙。因此對于同一個(gè)數(shù)據(jù)的并發(fā)操作,悲觀鎖采取加鎖的形式讽营。悲觀的認(rèn)為虎忌,不加鎖的并發(fā)操作一定會(huì)出問題。樂觀鎖則認(rèn)為對于同一個(gè)數(shù)據(jù)的并發(fā)操作橱鹏,是不會(huì)發(fā)生修改的膜蠢。在更新數(shù)據(jù)的時(shí)候,會(huì)采用嘗試更新莉兰,不斷重新的方式更新數(shù)據(jù)挑围。樂觀的認(rèn)為,不加鎖的并發(fā)操作是沒有事情的糖荒。從上面的描述我們可以看出杉辙,悲觀鎖適合寫操作非常多的場景,樂觀鎖適合讀操作非常多的場景捶朵,不加鎖會(huì)帶來大量的性能提升蜘矢。悲觀鎖在Java中的使用,就是利用各種鎖泉孩。樂觀鎖在Java中的使用硼端,是無鎖編程,常常采用的是CAS算法寓搬,典型的例子就是原子類珍昨,通過CAS自旋實(shí)現(xiàn)原子操作的更新。
獨(dú)享鎖/共享鎖
獨(dú)享鎖是指該鎖一次只能被一個(gè)線程所持有句喷。共享鎖是指該鎖可被多個(gè)線程所持有镣典。對于Java ReentrantLock而言,其是獨(dú)享鎖唾琼。但是對于Lock的另一個(gè)實(shí)現(xiàn)類ReentrantReadWriteLock兄春,其讀鎖是共享鎖,其寫鎖是獨(dú)享鎖锡溯。讀鎖的共享鎖可保證并發(fā)讀是非常高效的赶舆,讀寫哑姚,寫讀 ,寫寫的過程是互斥的芜茵。獨(dú)享鎖與共享鎖也是通過AQS來實(shí)現(xiàn)的叙量,通過實(shí)現(xiàn)不同的方法,來實(shí)現(xiàn)獨(dú)享或者共享九串。對于Synchronized而言绞佩,當(dāng)然是獨(dú)享鎖。
互斥鎖/讀寫鎖
上面講的獨(dú)享鎖/共享鎖就是一種廣義的說法猪钮,互斥鎖/讀寫鎖就是具體的實(shí)現(xiàn)品山。互斥鎖在Java中的具體實(shí)現(xiàn)就是ReentrantLock烤低,讀寫鎖在Java中的具體實(shí)現(xiàn)就是ReentrantReadWriteLock
手寫代碼肘交,線程1打印“A”,線程2打印“B”拂玻,按順序輸出“ABABABA...”
//摘自 https://blog.csdn.net/u012110719/article/details/47161789
public class MyThreadPrinter2 implements Runnable {
private String name;
private Object prev;
private Object self;
private MyThreadPrinter2(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
count--;
try{
Thread.sleep(1);
}
catch (InterruptedException e){
e.printStackTrace();
}
self.notify();
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
new Thread(pa).start();
Thread.sleep(10);
new Thread(pb).start();
Thread.sleep(10);
new Thread(pc).start();
Thread.sleep(10);
}
}
//Java5以后的Semaphore實(shí)現(xiàn)的話會(huì)比較簡單
public class ThreadSync {
static class ConditionThread extends Thread {
ConditionThread(Semaphore preCond, Semaphore postCond, String text) {
this.preCond = preCond;
this.postCond = postCond;
this.text = text;
}
private Semaphore preCond;
private Semaphore postCond;
private String text;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
preCond.acquire();
System.out.print(text);
postCond.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Semaphore semaphoreA = new Semaphore(0);
Semaphore semaphoreB = new Semaphore(1);
Thread threadA = new ConditionThread(semaphoreC, semaphoreA, "A");
Thread threadB = new ConditionThread(semaphoreA, semaphoreB, "B");
threadA.start();
threadB.start();
threadA.join();
threadB.join();
}
}