Android/Java 多線程分享

這篇博客主要有以下幾點(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)入就緒卖漫。

網(wǎng)上找的線程的幾種狀態(tài)轉(zhuǎ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ī)制詳解》叶圃。

參考來自[Java多線程 五]---JAVA鎖有哪些種類

常見的鎖

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();  
    }  
}  
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末酸些,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子檐蚜,更是在濱河造成了極大的恐慌魄懂,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闯第,死亡現(xiàn)場離奇詭異市栗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)咳短,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門填帽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咙好,你說我怎么就攤上這事篡腌。” “怎么了勾效?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵嘹悼,是天一觀的道長。 經(jīng)常有香客問我杨伙,道長,這世上最難降的妖魔是什么限匣? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任毁菱,我火速辦了婚禮锌历,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辩涝。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布捉邢。 她就那樣靜靜地躺著,像睡著了一般伏伐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上藐翎,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天材蹬,我揣著相機(jī)與錄音,去河邊找鬼吝镣。 笑死堤器,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的末贾。 我是一名探鬼主播闸溃,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拱撵!你這毒婦竟也來了辉川?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤拴测,失蹤者是張志新(化名)和其女友劉穎乓旗,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體集索,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屿愚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抄谐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渺鹦。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蛹含,靈堂內(nèi)的尸體忽然破棺而出毅厚,到底是詐尸還是另有隱情,我是刑警寧澤浦箱,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布吸耿,位于F島的核電站祠锣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏咽安。R本人自食惡果不足惜伴网,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望妆棒。 院中可真熱鬧澡腾,春花似錦、人聲如沸糕珊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽红选。三九已至,卻和暖如春喇肋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背甚侣。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工渺绒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留菱鸥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓殷绍,卻偏偏與公主長得像主到,于是被迫代替她去往敵國和親登钥。 傳聞我的和親對象是個(gè)殘疾皇子娶靡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

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

  • Android的應(yīng)用層都是Java寫的塔鳍,所以很多語法上的東西就是Java的,只不過某些類在原有基礎(chǔ)上進(jìn)行了包裝腔寡,畢...
    Poseidon_Wang閱讀 234評論 0 1
  • 當(dāng)某個(gè)應(yīng)用組件啟動(dòng)且該應(yīng)用沒有運(yùn)行其他任何組件時(shí)放前,Android 系統(tǒng)會(huì)使用單個(gè)執(zhí)行線程為應(yīng)用啟動(dòng)新的 Linux...
    小蕓論閱讀 1,691評論 0 12
  • 又來到了一個(gè)老生常談的問題犀斋,應(yīng)用層軟件開發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢? 今天就這個(gè)問題開始却舀,來談?wù)劜?..
    tangsl閱讀 4,124評論 0 23
  • 書名:《精力管理》 作者:吉姆·洛爾 托尼·施瓦茨 金句:目標(biāo)會(huì)激發(fā)決心锤灿。 分享:你找到人生目標(biāo)了嗎? “你的人生...
    精力滿滿閱讀 166評論 0 0
  • 筆者按:家長對子女的愛是與生俱來的,無可厚非状囱。但中國傳統(tǒng)更加注重“中庸”,任何事情要適度亭枷,否則會(huì)過猶不及...
    始終爬行的蝸牛閱讀 651評論 0 0