Java并發(fā)編程基礎(chǔ)

Java從誕生開始就明智地選擇了內(nèi)置對多線程的支持轩性,這使得Java語言相比同一時(shí)期的其他語言具有明顯的優(yōu)勢声登。線程作為操作系統(tǒng)調(diào)度的最小單元,多個(gè)線程能夠同時(shí)執(zhí)行揣苏,這將顯著提升程序性能悯嗓,在多核環(huán)境中表現(xiàn)得更加明顯。但是卸察,過多地創(chuàng)建線程和對線程的不當(dāng)管理也容易造成問題脯厨。本章將著重介紹Java并發(fā)編程的基礎(chǔ)知識(shí),從啟動(dòng)一個(gè)線程到線程間不同的通信方式坑质,最后通過簡單的線程池示例以及應(yīng)用(簡單的Web服務(wù)器)來串聯(lián)本章所介紹的內(nèi)容合武。

線程簡介

什么是線程

現(xiàn)代操作系統(tǒng)在運(yùn)行一個(gè)程序時(shí),會(huì)為其創(chuàng)建一個(gè)進(jìn)程涡扼。例如稼跳,啟動(dòng)一個(gè)Java程序,操作系統(tǒng)就會(huì)創(chuàng)建一個(gè)Java進(jìn)程〕曰Γ現(xiàn)代操作系統(tǒng)調(diào)度的最小單元是線程汤善,也叫輕量級(jí)進(jìn)程(Light Weight Process),在一個(gè)進(jìn)程里可以創(chuàng)建多個(gè)線程,這些線程都擁有各自的計(jì)數(shù)器红淡、堆棧和局部變量等屬性不狮,并且能夠訪問共享的內(nèi)存變量。處理器在這些線程上高速切換在旱,讓使用者感覺到這些線程在同時(shí)執(zhí)行摇零。

一個(gè)Java程序從main()方法開始執(zhí)行,然后按照既定的代碼邏輯執(zhí)行颈渊,看似沒有其他線程參與遂黍,但實(shí)際上Java程序天生就是多線程程序终佛,因?yàn)閳?zhí)行main()方法的是一個(gè)名稱為main的線程俊嗽。下面使用JMX來查看一個(gè)普通的Java程序包含哪些線程,如代碼清單所示铃彰。

public class MultiThread {

? ? public static void main(String[] args) {

? ? ? ? // 獲取Java線程管理MXBean? ? ? ?

? ? ? ? ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

? ? ? ? // 不需要獲取同步的monitor和synchronizer信息绍豁,僅獲取線程和線程堆棧信息

? ? ? ? ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);

? ? ? ? // 遍歷線程信息,僅打印線程ID和線程名稱信息? ? ? ?

? ? ? ? for (ThreadInfo threadInfo : threadInfos) {

? ? ? ? ? ? System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());

? ? ? ? ? ? }

? ? ? ? }

}

輸出如下所示(輸出內(nèi)容可能不同)牙捉。

[4] Signal Dispatcher? ? // 分發(fā)處理發(fā)送給JVM信號(hào)的線程

[3] Finalizer? ? ? ? ? ? // 調(diào)用對象finalize方法的線程

[2] Reference Handler? ? // 清除Reference的線程

[1] main? ? ? ? ? ? ? ? // main線程竹揍,用戶程序入口

可以看到,一個(gè)Java程序的運(yùn)行不僅僅是main()方法的運(yùn)行邪铲,而是main線程和多個(gè)其他線程的同時(shí)運(yùn)行芬位。

為什么要使用多線程

執(zhí)行一個(gè)簡單的“Hello, World!”,卻啟動(dòng)了那么多的“無關(guān)”線程带到,是不是把簡單的問題復(fù)雜化了昧碉?當(dāng)然不是,因?yàn)檎_使用多線程揽惹,總是能夠給開發(fā)人員帶來顯著的好處被饿,而使用多線程的原因主要有以下幾點(diǎn)。

(1)更多的處理器核心

隨著處理器上的核心數(shù)量越來越多搪搏,以及超線程技術(shù)的廣泛運(yùn)用狭握,現(xiàn)在大多數(shù)計(jì)算機(jī)都比以往更加擅長并行計(jì)算,而處理器性能的提升方式疯溺,也從更高的主頻向更多的核心發(fā)展论颅。如何利用好處理器上的多個(gè)核心也成了現(xiàn)在的主要問題。

線程是大多數(shù)操作系統(tǒng)調(diào)度的基本單元囱嫩,一個(gè)程序作為一個(gè)進(jìn)程來運(yùn)行恃疯,程序運(yùn)行過程中能夠創(chuàng)建多個(gè)線程,而一個(gè)線程在一個(gè)時(shí)刻只能運(yùn)行在一個(gè)處理器核心上挠说。試想一下澡谭,一個(gè)單線程程序在運(yùn)行時(shí)只能使用一個(gè)處理器核心,那么再多的處理器核心加入也無法顯著提升該程序的執(zhí)行效率。相反蛙奖,如果該程序使用多線程技術(shù)潘酗,將計(jì)算邏輯分配到多個(gè)處理器核心上,就會(huì)顯著減少程序的處理時(shí)間雁仲,并且隨著更多處理器核心的加入而變得更有效率仔夺。

(2)更快的響應(yīng)時(shí)間

有時(shí)我們會(huì)編寫一些較為復(fù)雜的代碼(這里的復(fù)雜不是說復(fù)雜的算法,而是復(fù)雜的業(yè)務(wù)邏輯)攒砖,例如缸兔,一筆訂單的創(chuàng)建,它包括插入訂單數(shù)據(jù)吹艇、生成訂單快照惰蜜、發(fā)送郵件通知賣家和記錄貨品銷售數(shù)量等。用戶從單擊“訂購”按鈕開始受神,就要等待這些操作全部完成才能看到訂購成功的結(jié)果抛猖。但是這么多業(yè)務(wù)操作,如何能夠讓其更快地完成呢鼻听?

在上面的場景中财著,可以使用多線程技術(shù),即將數(shù)據(jù)一致性不強(qiáng)的操作派發(fā)給其他線程處理(也可以使用消息隊(duì)列)撑碴,如生成訂單快照撑教、發(fā)送郵件等。這樣做的好處是響應(yīng)用戶請求的線程能夠盡可能快地處理完成醉拓,縮短了響應(yīng)時(shí)間伟姐,提升了用戶體驗(yàn)。

(3)更好的編程模型

Java為多線程編程提供了良好廉嚼、考究并且一致的編程模型玫镐,使開發(fā)人員能夠更加專注于問題的解決,即為所遇到的問題建立合適的模型怠噪,而不是絞盡腦汁地考慮如何將其多線程化恐似。一旦開發(fā)人員建立好了模型,稍做修改總是能夠方便地映射到Java提供的多線程編程模型上傍念。

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

現(xiàn)代操作系統(tǒng)基本采用時(shí)分的形式調(diào)度運(yùn)行的線程矫夷,操作系統(tǒng)會(huì)分出一個(gè)個(gè)時(shí)間片,線程會(huì)分配到若干時(shí)間片憋槐,當(dāng)線程的時(shí)間片用完了就會(huì)發(fā)生線程調(diào)度双藕,并等待著下次分配。線程分配到的時(shí)間片多少也就決定了線程使用處理器資源的多少阳仔,而線程優(yōu)先級(jí)就是決定線程需要多或者少分配一些處理器資源的線程屬性忧陪。

在Java線程中,通過一個(gè)整型成員變量priority來控制優(yōu)先級(jí),優(yōu)先級(jí)的范圍從1~10嘶摊,在線程構(gòu)建的時(shí)候可以通過setPriority(int)方法來修改優(yōu)先級(jí)延蟹,默認(rèn)優(yōu)先級(jí)是5,優(yōu)先級(jí)高的線程分配時(shí)間片的數(shù)量要多于優(yōu)先級(jí)低的線程叶堆。設(shè)置線程優(yōu)先級(jí)時(shí)阱飘,針對頻繁阻塞(休眠或者I/O操作)的線程需要設(shè)置較高優(yōu)先級(jí),而偏重計(jì)算(需要較多CPU時(shí)間或者偏運(yùn)算)的線程則設(shè)置較低的優(yōu)先級(jí)虱颗,確保處理器不會(huì)被獨(dú)占沥匈。在不同的JVM以及操作系統(tǒng)上,線程規(guī)劃會(huì)存在差異忘渔,有些操作系統(tǒng)甚至?xí)雎詫€程優(yōu)先級(jí)的設(shè)定高帖,示例代碼清單。

Priority. java

public class Priority {

? ? private static volatile boolean notStart = true;

? ? private static volatile boolean notEnd = true;

? ? public static void main(String[] args) throws Exception {List<Job> jobs = new ArrayList<Job>();

? ? ? ? for (int i = 0; i < 10; i++) {int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;

? ? ? ? ? ? Job job = new Job(priority);jobs.add(job);Thread thread = new Thread(job, "Thread:" + i);

? ? ? ? ? ? thread.setPriority(priority);thread.start();}notStart = false;TimeUnit.SECONDS.sleep(10);

? ? ? ? notEnd = false;for (Job job : jobs) {

? ? ? ? ? ? System.out.println("Job Priority : " + job.priority + ",? ? ? ? ? ? ? ? ? ? Count : " + job.jobCount);}}

? ? static class Job implements Runnable {

? ? ? ? private int priority;

? ? ? ? private long jobCount;

? ? ? ? public Job(int priority) {this.priority = priority;}

? ? ? ? public void run() {while (notStart) {Thread.yield();}

? ? ? ? ? ? while (notEnd) {Thread.yield();jobCount++;}}

? ? }

}

運(yùn)行該示例辨萍,在筆者機(jī)器上對應(yīng)的輸出如下棋恼。

Job Priority : 1, Count : 1259592

Job Priority : 1, Count : 1260717

Job Priority : 1, Count : 1264510

Job Priority : 1, Count : 1251897

Job Priority : 1, Count : 1264060

Job Priority : 10, Count : 1256938

Job Priority : 10, Count : 1267663

Job Priority : 10, Count : 1260637

Job Priority : 10, Count : 1261705

Job Priority : 10, Count : 1259967

從輸出可以看到線程優(yōu)先級(jí)沒有生效,優(yōu)先級(jí)1和優(yōu)先級(jí)10的Job計(jì)數(shù)的結(jié)果非常相近锈玉,沒有明顯差距。這表示程序正確性不能依賴線程的優(yōu)先級(jí)高低义起。注意

線程優(yōu)先級(jí)不能作為程序正確性的依賴拉背,因?yàn)椴僮飨到y(tǒng)可以完全不用理會(huì)Java線程對于優(yōu)先級(jí)的設(shè)定。筆者的環(huán)境為:Mac OS X 10.10默终,Java版本為1.7.0_71椅棺,經(jīng)過筆者驗(yàn)證該環(huán)境下所有Java線程優(yōu)先級(jí)均為5(通過jstack查看),對線程優(yōu)先級(jí)的設(shè)置會(huì)被忽略齐蔽。另外两疚,嘗試在Ubuntu 14.04環(huán)境下運(yùn)行該示例,輸出結(jié)果也表示該環(huán)境忽略了線程優(yōu)先級(jí)的設(shè)置含滴。

線程的狀態(tài)

Java線程在運(yùn)行的生命周期中可能處于表4-1所示的6種不同的狀態(tài)诱渤,在給定的一個(gè)時(shí)刻,線程只能處于其中的一個(gè)狀態(tài)谈况。

Java線程的狀態(tài)

由圖可以看到勺美,線程創(chuàng)建之后,調(diào)用start()方法開始運(yùn)行碑韵。當(dāng)線程執(zhí)行wait()方法之后赡茸,線程進(jìn)入等待狀態(tài)。進(jìn)入等待狀態(tài)的線程需要依靠其他線程的通知才能夠返回到運(yùn)行狀態(tài)祝闻,而超時(shí)等待狀態(tài)相當(dāng)于在等待狀態(tài)的基礎(chǔ)上增加了超時(shí)限制占卧,也就是超時(shí)時(shí)間到達(dá)時(shí)將會(huì)返回到運(yùn)行狀態(tài)。當(dāng)線程調(diào)用同步方法時(shí),在沒有獲取到鎖的情況下华蜒,線程將會(huì)進(jìn)入到阻塞狀態(tài)舷蒲。線程在執(zhí)行Runnable的run()方法之后將會(huì)進(jìn)入到終止?fàn)顟B(tài)。

注意

Java將操作系統(tǒng)中的運(yùn)行和就緒兩個(gè)狀態(tài)合并稱為運(yùn)行狀態(tài)友多。阻塞狀態(tài)是線程阻塞在進(jìn)入synchronized關(guān)鍵字修飾的方法或代碼塊(獲取鎖)時(shí)的狀態(tài)牲平,但是阻塞在java. concurrent包中Lock接口的線程狀態(tài)卻是等待狀態(tài),因?yàn)閖ava.concurrent包中Lock接口對于阻塞的實(shí)現(xiàn)均使用了LockSupport類中的相關(guān)方法域滥。

往期精選

程序員必讀的基本功圖書纵柿,你讀了幾本?

轉(zhuǎn)架構(gòu)師你需要準(zhǔn)備哪些面試題目

Java并發(fā)機(jī)制的底層實(shí)現(xiàn)原理 -? synchronized和volatile

史上最適合初學(xué)者入門的Java基礎(chǔ)視頻免費(fèi)放送

Google面試官親授Java面試視頻

計(jì)算機(jī)系統(tǒng)基礎(chǔ)公開課-南京大學(xué)

年薪40w启绰,你需要具備什么技能昂儒。

每周推薦一本書 - 《大型網(wǎng)站技術(shù)架構(gòu)》

每周推薦一本書 - 《spring cloud》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市委可,隨后出現(xiàn)的幾起案子渊跋,更是在濱河造成了極大的恐慌,老刑警劉巖着倾,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拾酝,死亡現(xiàn)場離奇詭異,居然都是意外死亡卡者,警方通過查閱死者的電腦和手機(jī)蒿囤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崇决,“玉大人材诽,你說我怎么就攤上這事『闵担” “怎么了脸侥?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盈厘。 經(jīng)常有香客問我睁枕,道長,這世上最難降的妖魔是什么扑庞? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任譬重,我火速辦了婚禮,結(jié)果婚禮上罐氨,老公的妹妹穿的比我還像新娘臀规。我一直安慰自己,他們只是感情好栅隐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布塔嬉。 她就那樣靜靜地躺著玩徊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谨究。 梳的紋絲不亂的頭發(fā)上恩袱,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音胶哲,去河邊找鬼畔塔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鸯屿,可吹牛的內(nèi)容都是我干的澈吨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼寄摆,長吁一口氣:“原來是場噩夢啊……” “哼谅辣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起婶恼,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對情侶失蹤桑阶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后勾邦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚣录,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年检痰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了包归。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡铅歼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出换可,到底是詐尸還是另有隱情椎椰,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布沾鳄,位于F島的核電站慨飘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏译荞。R本人自食惡果不足惜瓤的,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吞歼。 院中可真熱鬧圈膏,春花似錦、人聲如沸篙骡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至尿褪,卻和暖如春睦擂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背杖玲。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工顿仇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人摆马。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓臼闻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親今膊。 傳聞我的和親對象是個(gè)殘疾皇子些阅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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

  • 轉(zhuǎn):《Java并發(fā)編程的藝術(shù)》 1 線程簡介 現(xiàn)代操作系統(tǒng)在運(yùn)行一個(gè)程序時(shí)市埋,會(huì)為其創(chuàng)建一個(gè)進(jìn)程。例如恕刘,啟動(dòng)一個(gè)Ja...
    沉淪2014閱讀 367評(píng)論 0 1
  • 1.線程簡介 ①什么是線程 現(xiàn)代操作系統(tǒng)調(diào)度的最小單元是線程缤谎,也叫輕量級(jí)進(jìn)程,在一個(gè)進(jìn)程里可以創(chuàng)建多個(gè)線程褐着,這些線...
    加夕閱讀 428評(píng)論 0 0
  • CPU 核心數(shù)和線程數(shù)的關(guān)系 目前的 CPU 有雙核坷澡,四核,八核含蓉,一般情況下频敛,它和線程數(shù)是1:1的對應(yīng)關(guān)系,也就是...
    未見哥哥閱讀 476評(píng)論 0 4
  • 線程簡介 線程優(yōu)先級(jí) 在 Java 線程中馅扣,通過一個(gè)整形成員變量 priority 來控制優(yōu)先級(jí)斟赚,優(yōu)先級(jí)的范圍從 ...
    沐小晨曦閱讀 280評(píng)論 0 0
  • 參考:《Java并發(fā)編程的藝術(shù)》第四章《Java多線程編程核心技術(shù)》博客 https://www.jianshu....
    莫小歸閱讀 434評(píng)論 0 0