Java并行程序

并行程序基礎(chǔ)

本文為《實(shí)戰(zhàn)java高并發(fā)程序設(shè)計(jì)》電子筆記罕袋,供個(gè)人查閱及裝逼浴讯,不具有參考性榆纽。
https://legacy.gitbook.com/book/jiapengcai/effective-java/details

1.2 幾個(gè)重要的概念

  • 并發(fā)偏重于多個(gè)任務(wù)交替執(zhí)行,而多個(gè)任務(wù)之間有可能還是串行的饥侵。而并行是真正意義上的“同時(shí)執(zhí)行
  • 臨界區(qū)
  • 阻塞(Blocking)和非阻塞(Non-Blocking)
    阻塞:一個(gè)線程占用了臨界區(qū)的資源躏升,其他所有需要這個(gè)資源的線程就必須在這個(gè)臨界區(qū)中進(jìn)行等待狼忱。等待會(huì)導(dǎo)致線程掛起钻弄,這種情況就是阻塞窘俺。
    非阻塞:沒有一個(gè)線程可以妨礙其他線程執(zhí)行批销。所有的線程都會(huì)嘗試不斷前向執(zhí)行
  • 死鎖(Deadlock)染坯、饑餓(Starvation)和活鎖(Livelock)

1.3 并發(fā)級(jí)別

由于臨界區(qū)的存在单鹿,多線程之間的并發(fā)必須受到控制。根據(jù)控制并發(fā)的策略,我們可以把并發(fā)的級(jí)別進(jìn)行分類劲妙,大致上可以分為阻塞湃鹊、無饑餓、無障礙镣奋、無鎖币呵、無等待幾種。

  • 阻塞(Blocking)
    在其他線程釋放資源之前侨颈,當(dāng)前線程無法繼續(xù)執(zhí)行余赢。當(dāng)我們使用synchronized關(guān)鍵字,或者重入鎖時(shí)妻柒,我們得到的就是阻塞的線程。

無論是synchronized或者重入鎖耘分,都會(huì)試圖在執(zhí)行后續(xù)代碼前举塔,得到臨界區(qū)的鎖,如果得不到求泰,線程就會(huì)被掛起等待央渣,直到占有了所需資源為止。

  • 無饑餓(Starvation-Free)
    不同線程的優(yōu)先級(jí)相等
  • 無障礙(Obstruction-Free)
    一種最弱的非阻塞調(diào)度拜秧。兩個(gè)線程無障礙運(yùn)行痹屹,他們不會(huì)因?yàn)榕R界區(qū)的問題導(dǎo)致一方掛起。但數(shù)據(jù)出現(xiàn)異常枉氮,則立即回滾自己的修改志衍,確保數(shù)據(jù)正確。
  • 無鎖(Lock-Free)
    無鎖的并行都是無障礙的聊替。在無鎖的情況下楼肪,所有的線程都能嘗試對(duì)臨界區(qū)進(jìn)行訪問,但不同的是惹悄,無鎖的并發(fā)保證必然有一個(gè)線程能夠在有限步內(nèi)完成操作離開臨界區(qū)春叫。
  • 無等待(Wait-Free)
    無鎖只要求有一個(gè)線程可以在有限步內(nèi)完成操作,而無等待則在無鎖的基礎(chǔ)上更進(jìn)一步進(jìn)行擴(kuò)展泣港。它要求所有的線程都必須在有限步內(nèi)完成暂殖,這樣就不會(huì)引起饑餓問題。

一種典型的無等待結(jié)構(gòu)就是RCU(Read-Copy-Update)当纱。它的基本思想是呛每,對(duì)數(shù)據(jù)的讀可以不加控制。因此坡氯,所有的讀線程都是無等待的晨横,它們既不會(huì)被鎖定等待也不會(huì)引起任何沖突洋腮。但在寫數(shù)據(jù)的時(shí)候,先取得原始數(shù)據(jù)的副本手形,接著只修改副本數(shù)據(jù)(這就是為什么讀可以不加控制)啥供,修改完成后,在合適的時(shí)機(jī)回寫數(shù)據(jù)库糠。

1.4關(guān)于并行的兩個(gè)重要定律

1.5 JMM

我們需要在深入了解并行機(jī)制的前提下伙狐,再定義一種規(guī)則,保證多個(gè)線程間可以有效地瞬欧、正確地協(xié)同工作鳞骤。而JMM也就是為此而生的。
JMM的關(guān)鍵技術(shù)點(diǎn)都是圍繞著多線程的原子性黍判、可見性和有序性來建立的

  • 原子性(Atomicity)
    原子性是指一個(gè)操作是不可中斷的豫尽。即使是在多個(gè)線程一起執(zhí)行的時(shí)候,一個(gè)操作一旦開始顷帖,就不會(huì)被其他線程干擾美旧。
  • 可見性(Visibility)
    出現(xiàn)一個(gè)線程的修改不會(huì)立即被其他線程察覺的情況

2.2 線程的基本操作

1.新建線程
通過繼承Thread類或?qū)崿F(xiàn)Runnable接口

start()方法就新建一個(gè)線程并讓這個(gè)線程執(zhí)行run()方法

2.停止線程
一般無需手動(dòng)關(guān)閉線程

Thread.stop()方法在結(jié)束線程時(shí),會(huì)直接終止線程贬墩,并且會(huì)立即釋放這個(gè)線程所持有的鎖榴嗅。而這些鎖恰恰是用來維持對(duì)象一致性的。故stop方法不使用

3.線程中斷

public void Thread.interrupt()               // 中斷線程
public boolean Thread.isInterrupted()        // 判斷是否被中斷
public static boolean Thread.interrupted()   // 判斷是否被中斷陶舞,并清除當(dāng)前中斷狀態(tài)
  • Thead.sleep()方法:讓當(dāng)前線程休眠若干時(shí)間

Thread.sleep()方法會(huì)讓當(dāng)前線程休眠若干時(shí)間嗽测,它會(huì)拋出一個(gè)InterruptedException中斷異常。InterruptedException不是運(yùn)行時(shí)異常肿孵,也就是說程序必須捕獲并且處理它唠粥,當(dāng)線程在sleep()休眠時(shí),如果被中斷停做,這個(gè)異常就會(huì)產(chǎn)生

01 public static void main(String[] args) throws InterruptedException {
02     Thread t1=new Thread(){
03         @Override
04         public void run(){
05             while(true){
06                 if(Thread.currentThread().isInterrupted()){
07                     System.out.println("Interruted!");
08                     break;
09                 }
10                 try {
11                     Thread.sleep(2000);
12                 } catch (InterruptedException e) {
13                     System.out.println("Interruted When Sleep");
14                     //設(shè)置中斷狀態(tài)
15                     Thread.currentThread().interrupt();
16                 }
17                 Thread.yield();
18             }
19         }
20     };
21     t1.start();
22     Thread.sleep(2000);
23     t1.interrupt();
24 }

Thread.sleep()方法由于中斷而拋出異常晤愧,此時(shí),它會(huì)清除中斷標(biāo)記蛉腌,如果不加處理官份,那么在下一次循環(huán)開始時(shí),就無法捕獲這個(gè)中斷烙丛,故在異常處理中舅巷,再次設(shè)置中斷標(biāo)記位。

4.等待(wait)和通知(notify)
這兩個(gè)方法并不是在Thread類中的河咽,而是輸出Object類钠右。這也意味著任何對(duì)象都可以調(diào)用這兩個(gè)方法。

線程A中库北,調(diào)用了obj.wait()方法爬舰,那么線程A就會(huì)停止繼續(xù)執(zhí)行,而轉(zhuǎn)為等待狀態(tài)寒瓦。線程A會(huì)一直等到其他線程調(diào)用了obj.notify()方法為止情屹。這時(shí),obj對(duì)象就儼然成為多個(gè)線程之間的有效通信手段杂腰。

image.png

注意:
必須在包含synchronzied的語(yǔ)句中才可以調(diào)用 wait方法

image.png

6.等待線程結(jié)束(join)和謙讓(yield)

public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException

第一個(gè)join()方法表示無限等待垃你,它會(huì)一直阻塞當(dāng)前線程,直到目標(biāo)線程執(zhí)行完畢喂很。第二個(gè)方法給出了一個(gè)最大等待時(shí)間惜颇,超過最大時(shí)間線程就繼續(xù)執(zhí)行。

public volatile static int i=0;
    public static class AddThread extends Thread{
        @Override
        public void run() {
            for(i=0;i<10000000;i++);
        }
    }
    public static void main(String[] args) throws InterruptedException {
        AddThread at=new AddThread();
        at.start();
        at.join();
        //主線程等待子線程執(zhí)行后再執(zhí)行
        System.out.println(i);
    }

join()的本質(zhì)是讓調(diào)用線程wait()在當(dāng)前線程對(duì)象實(shí)例

public static native void yield();
//讓出當(dāng)前占用cpu少辣,但接下來會(huì)繼續(xù)參與搶奪

2.3volatile與Java內(nèi)存模型(JMM)

當(dāng)你用volatile去申明一個(gè)變量時(shí)凌摄,就等于告訴了虛擬機(jī),這個(gè)變量極有可能會(huì)被某些程序或者線程修改漓帅。為了確保這個(gè)變量被修改后锨亏,應(yīng)用程序范圍內(nèi)的所有線程都能夠“看到”這個(gè)改動(dòng),虛擬機(jī)就必須采用一些特殊的手段忙干,保證這個(gè)變量的可見性等特點(diǎn)器予。

  • volatile并不能代替鎖,它也無法保證一些復(fù)合操作的原子性捐迫。比如下面的例子乾翔,通過volatile是無法保證i++的原子性操作的:
01 static volatile int i=0;
02 public static class PlusTask implements Runnable{
03     @Override
04     public void run() {
05         for(int k=0;k<10000;k++)
06             i++;
07     }
08 }
09
10 public static void main(String[] args) throws InterruptedException {
11     Thread[] threads=new Thread[10];
12     for(int i=0;i<10;i++){
13         threads[i]=new Thread(new PlusTask());
14         threads[i].start();
15     }
16     for(int i=0;i<10;i++){
17         threads[i].join();
18     }
19
20     System.out.println(i);
21    }

執(zhí)行上述代碼,如果第6行i++是原子性的施戴,那么最終的值應(yīng)該是100000(10個(gè)線程各累加10000次)反浓。但實(shí)際上,上述代碼的輸出總是會(huì)小于100000赞哗。

  • volatile能保證數(shù)據(jù)的可見性和有序性

2.6 線程優(yōu)先級(jí)

public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

2.7 線程安全的概念與synchronized

volatile并不能真正的保證線程安全勾习。它只能確保一個(gè)線程修改了數(shù)據(jù)后,其他線程能夠看到這個(gè)改動(dòng)懈玻。但當(dāng)兩個(gè)線程同時(shí)修改某一個(gè)數(shù)據(jù)時(shí)巧婶,卻依然會(huì)產(chǎn)生沖突

01 public class AccountingVol implements Runnable{
02     static AccountingVol instance=new AccountingVol();
03     static volatile int i=0;
04     public static void increase(){
05         i++;
06     }
07     @Override
08     public void run() {
09         for(int j=0;j<10000000;j++){
10             increase();
11         }
12     }
13     public static void main(String[] args) throws InterruptedException {
14         Thread t1=new Thread(instance);
15         Thread t2=new Thread(instance);
16         t1.start();t2.start();
17         t1.join();t2.join();
18         System.out.println(i);
19     }
20 }

很多時(shí)候,i的最終值會(huì)小于20000000涂乌。這就是因?yàn)閮蓚€(gè)線程同時(shí)對(duì)i進(jìn)行寫入時(shí)艺栈,其中一個(gè)線程的結(jié)果會(huì)覆蓋另外一個(gè)(雖然這個(gè)時(shí)候i被聲明為volatile變量)。線程1和線程2同時(shí)讀取i為0湾盒,并各自計(jì)算得到i=1湿右,并先后寫入這個(gè)結(jié)果,因此罚勾,雖然i++被執(zhí)行了2次毅人,但是實(shí)際i的值只增加了1吭狡。

要從根本上解決這個(gè)問題,我們就必須保證多個(gè)線程在對(duì)i進(jìn)行操作時(shí)完全同步丈莺,使用關(guān)鍵字synchronized來實(shí)現(xiàn)這個(gè)功能

關(guān)鍵字synchronized的作用是實(shí)現(xiàn)線程間的同步划煮。它的工作是對(duì)同步的代碼加鎖,使得每一次缔俄,只能有一個(gè)線程進(jìn)入同步塊弛秋,從而保證線程間的安全性

關(guān)鍵字synchronized可以有多種用法。這里做一個(gè)簡(jiǎn)單的整理俐载。

  • 指定加鎖對(duì)象:對(duì)給定對(duì)象加鎖蟹略,進(jìn)入同步代碼前要獲得給定對(duì)象的鎖。
  • 直接作用于實(shí)例方法:相當(dāng)于對(duì)當(dāng)前實(shí)例加鎖遏佣,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖挖炬。
  • 直接作用于靜態(tài)方法:相當(dāng)于對(duì)當(dāng)前類加鎖,進(jìn)入同步代碼前要獲得當(dāng)前類的鎖状婶。
01 public class AccountingSync2 implements Runnable{
02     static AccountingSync2 instance=new AccountingSync2();
03     static int i=0;
04     public synchronized void increase(){
05         i++;
06     }
07     @Override
08     public void run() {
09         for(int j=0;j<10000000;j++){
10             increase();
11         }
12     }
13     public static void main(String[] args) throws InterruptedException {
14         Thread t1=new Thread(instance);
15         Thread t2=new Thread(instance);
16         t1.start();t2.start();
17         t1.join();t2.join();
18         System.out.println(i);
19     }
20 }

這兩個(gè)線程都指向同一個(gè)Runnable接口實(shí)例(instance對(duì)象)茅茂,這樣才能保證兩個(gè)線程在工作時(shí),能夠關(guān)注到同一個(gè)對(duì)象鎖上去太抓,從而保證線程安全空闲。
一個(gè)錯(cuò)誤的示例

01 public class AccountingSyncBad implements Runnable{
02     static int i=0;
03     public synchronized void increase(){
04         i++;
05     }
06     @Override
07     public void run() {
08         for(int j=0;j<10000000;j++){
09             increase();
10         }
11     }
12     public static void main(String[] args) throws InterruptedException {
13         Thread t1=new Thread(new AccountingSyncBad());
14         Thread t2=new Thread(new AccountingSyncBad());
15         t1.start();t2.start();
16         t1.join();t2.join();
17         System.out.println(i);
18     }
19 }

但我們只要簡(jiǎn)單地修改上述代碼,就能使其正確執(zhí)行走敌。那就是使用synchronized的第三種用法,將其作用于靜態(tài)方法掉丽。將increase()方法修改如下:
這樣跌榔,即使兩個(gè)線程指向不同的Runnable對(duì)象,但由于方法塊需要請(qǐng)求的是當(dāng)前類的鎖,而非當(dāng)前實(shí)例锭部,因此,線程間還是可以正確同步匪傍。

public static synchronized void increase(){
    i++;
}

被synchronized限制的多個(gè)線程是串行執(zhí)行的

2.8 程序中的幽靈:隱蔽的錯(cuò)誤

  • 并發(fā)下的ArrayList

注意:改進(jìn)的方法很簡(jiǎn)單,使用線程安全的Vector代替ArrayList即可帽撑。

  • 并發(fā)下詭異的HashMap

最簡(jiǎn)單的解決方案就是使用ConcurrentHashMap代替HashMap。

  • 錯(cuò)誤的加鎖
    比如加在不可變的int上

JDK并發(fā)包

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肋层,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子肃拜,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異右锨,居然都是意外死亡轧抗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門涣楷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來分唾,“玉大人,你說我怎么就攤上這事狮斗≌狼牵” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵碳褒,是天一觀的道長(zhǎng)折砸。 經(jīng)常有香客問我,道長(zhǎng)沙峻,這世上最難降的妖魔是什么睦授? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮摔寨,結(jié)果婚禮上去枷,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好删顶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布竖螃。 她就那樣靜靜地躺著,像睡著了一般逗余。 火紅的嫁衣襯著肌膚如雪特咆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天录粱,我揣著相機(jī)與錄音腻格,去河邊找鬼。 笑死啥繁,一個(gè)胖子當(dāng)著我的面吹牛菜职,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播输虱,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼些楣,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼脂凶!你這毒婦竟也來了宪睹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蚕钦,失蹤者是張志新(化名)和其女友劉穎亭病,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘶居,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡罪帖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了邮屁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片整袁。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖佑吝,靈堂內(nèi)的尸體忽然破棺而出坐昙,到底是詐尸還是另有隱情,我是刑警寧澤芋忿,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布炸客,位于F島的核電站,受9級(jí)特大地震影響戈钢,放射性物質(zhì)發(fā)生泄漏痹仙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一殉了、第九天 我趴在偏房一處隱蔽的房頂上張望开仰。 院中可真熱鬧,春花似錦、人聲如沸抖所。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)田轧。三九已至暴匠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間傻粘,已是汗流浹背每窖。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弦悉,地道東北人摇庙。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓刨裆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子汁果,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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

  • 本文首發(fā)于我的個(gè)人博客:尾尾部落 本文是我刷了幾十篇一線互聯(lián)網(wǎng)校招java后端開發(fā)崗位的面經(jīng)后總結(jié)的多線程相關(guān)題目...
    繁著閱讀 2,010評(píng)論 0 7
  • 線程池ThreadPoolExecutor corepoolsize:核心池的大小,默認(rèn)情況下拍屑,在創(chuàng)建了線程池之后...
    irckwk1閱讀 728評(píng)論 0 0
  • 線程安全 當(dāng)多個(gè)線程訪問一個(gè)對(duì)象時(shí)墅茉,如果不用考慮這些線程在運(yùn)行時(shí)環(huán)境下的調(diào)度和交替執(zhí)行,也不需要進(jìn)行額外的同步良拼,或...
    閩越布衣閱讀 768評(píng)論 0 6
  • 進(jìn)程和線程 進(jìn)程 所有運(yùn)行中的任務(wù)通常對(duì)應(yīng)一個(gè)進(jìn)程,當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程.進(jìn)程是處于運(yùn)行過程中...
    勝浩_ae28閱讀 5,110評(píng)論 0 23
  • 文章來源:http://www.54tianzhisheng.cn/2017/06/04/Java-Thread/...
    beneke閱讀 1,486評(píng)論 0 1