摘要:線程被稱為輕量級(jí)進(jìn)程。在大多數(shù)操作系統(tǒng)中,線程都是最基本的調(diào)度單位。在多線程程序中,,還存在由于使用多線程而引入的其他問(wèn)題。由于多線程訪問(wèn)無(wú)狀態(tài)對(duì)象的行為不會(huì)影響到其他線程中操作的正確性,因此無(wú)狀態(tài)對(duì)象一定是線程安全的。
概述
最近遇到了些并發(fā)的問(wèn)題,恰巧也有朋友問(wèn)我類似的問(wèn)題,無(wú)奈并發(fā)基礎(chǔ)知識(shí)過(guò)弱,只大概了解使用一些同步機(jī)制和并發(fā)工具包類,沒有形成一個(gè)完整的知識(shí)體系,并不能給出一個(gè)良好的解決方案。深知自己就是個(gè)弟弟,趁著周末有空,就趕緊把之前買的并發(fā)編程實(shí)戰(zhàn)拿起來(lái),擦擦灰,惡補(bǔ)一下....
并發(fā)簡(jiǎn)史在介紹并發(fā)前,我們先來(lái)簡(jiǎn)單的了解下計(jì)算機(jī)的發(fā)展歷史。早期的計(jì)算機(jī)是不包含操作系統(tǒng)的,他們可以使用計(jì)算機(jī)上所有的資源,計(jì)算機(jī)從頭到尾也就只執(zhí)行著一個(gè)程序。在這種裸機(jī)環(huán)境下,編寫和運(yùn)行程序?qū)⒆兊姆浅B闊?,并且只運(yùn)行一個(gè)程序?qū)τ谟?jì)算機(jī)來(lái)說(shuō)也是一種嚴(yán)重的浪費(fèi)。為了解決這個(gè)問(wèn)題,操作系統(tǒng)閃亮登場(chǎng),計(jì)算機(jī)每次都能運(yùn)行多個(gè)程序,每個(gè)程序都是一個(gè)多帶帶的進(jìn)程。操作系統(tǒng)為每一個(gè)進(jìn)程分配各種資源,比如:內(nèi)存、文件句柄等。如果需要的話,不同的進(jìn)程之間可以通過(guò)通信機(jī)制來(lái)交換數(shù)據(jù)。
操作系統(tǒng)的出現(xiàn),主要給我們解決了這幾個(gè)問(wèn)題,資源利用率的提高,程序之間的公平性和便利性。
資源利用率
有些情況下,程序必須等待某個(gè)外部操作完成才能繼續(xù)進(jìn)行。比如當(dāng)我們向計(jì)算機(jī)復(fù)制數(shù)據(jù)的時(shí)候,此時(shí)只有io在工作,如果在等待復(fù)制的時(shí)間,計(jì)算機(jī)可以運(yùn)行其他程序,無(wú)疑大大提高了資源的利用率。
公平性
操作系統(tǒng)常見的一種方式就是通過(guò)粗粒度的時(shí)間分片來(lái)使用戶和程序能共享計(jì)算機(jī)資源,而不是一個(gè)程序從頭運(yùn)行到尾,然后再啟動(dòng)下一個(gè)程序。想一想,你可以用著自己的個(gè)人pc,打著游戲,聽著歌,和女朋友聊著天,計(jì)算機(jī)資源會(huì)來(lái)回切換,只不過(guò)因?yàn)樗俣群芸?,給我們的感覺就像是同時(shí)發(fā)生一樣,這一切都要?dú)w功于操作系統(tǒng)的調(diào)配。
便利性
一般來(lái)說(shuō),在計(jì)算多個(gè)任務(wù)時(shí),應(yīng)該編寫多個(gè)程序,每個(gè)程序在執(zhí)行一個(gè)任務(wù)時(shí)并在需要時(shí)進(jìn)行通信,這比只編寫一個(gè)程序來(lái)計(jì)算所有任務(wù)更容易實(shí)現(xiàn)。
線程的出現(xiàn)和進(jìn)程的出現(xiàn)是一個(gè)道理的,只不過(guò)一個(gè)調(diào)配的是一個(gè)進(jìn)程內(nèi)的資源問(wèn)題,另一個(gè)是調(diào)配一臺(tái)計(jì)算機(jī)之間的資源。進(jìn)程允許存在多個(gè)線程,并且線程之間會(huì)共享進(jìn)程范圍內(nèi)的資源(內(nèi)存和文件句柄),但每個(gè)線程都有自己的程序計(jì)數(shù)器、棧等,而且同一個(gè)程序的多個(gè)線程可以同時(shí)被調(diào)度到多個(gè)cpu上運(yùn)行。
線程被稱為輕量級(jí)進(jìn)程。在大多數(shù)操作系統(tǒng)中,線程都是最基本的調(diào)度單位。如果沒有統(tǒng)一的協(xié)同機(jī)制,線程將彼此獨(dú)立運(yùn)行,由于同一個(gè)進(jìn)程上的所有線程都將共享進(jìn)程的內(nèi)存空間,它們將訪問(wèn)相同的變量并在同一個(gè)堆上分配對(duì)象,這就需要一個(gè)更細(xì)粒度的數(shù)據(jù)共享機(jī)制,不然將造成不可預(yù)測(cè)的后果。
線程可以充分發(fā)揮多處理器的強(qiáng)大能力
避免單個(gè)線程阻塞而導(dǎo)致整個(gè)程序停頓
異步事件的簡(jiǎn)化處理
線程的風(fēng)險(xiǎn)安全性問(wèn)題
class ThreadSafeTest{ static int count; public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); for (int i=0;i<100;i++){ new Thread(new Runnable() { @Override public void run() { try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } for (int x=0;x<100;x++){ count++; } } }).start(); } countDownLatch.countDown(); Thread.sleep(5000); System.out.println("count:"+count); } } 輸出結(jié)果:count:9635
運(yùn)行結(jié)果:count:9955.我們的期待結(jié)果是10000,并且我們多次的運(yùn)行結(jié)果還可能不一樣。這個(gè)問(wèn)題主要在于count++并不是一個(gè)原子操作,它可以分為讀取count,count+1和計(jì)算結(jié)果寫回。如果在缺少同步的情況下,我們無(wú)法保證多線程情況下結(jié)果的正確性.
活躍性問(wèn)題
安全性的含義是永遠(yuǎn)不會(huì)發(fā)生錯(cuò)誤的事情,而活躍性的含義將是正確的事情最終會(huì)發(fā)生。當(dāng)某個(gè)操作無(wú)法繼續(xù)執(zhí)行下去,就會(huì)發(fā)生活躍性的問(wèn)題。在穿行程序中,無(wú)意中造成的無(wú)限循環(huán)就是活躍性問(wèn)題之一。此外分別還有死鎖、饑餓以及活鎖問(wèn)題。
死鎖:線程A在等待線程B釋放其擁有的資源,而線程B在等待線程A釋放其擁有的資源,這樣僵持不下,那么線程A、B就會(huì)永遠(yuǎn)等下去。
饑餓:最常見的饑餓問(wèn)題就是CPU時(shí)鐘周期問(wèn)題。如果在java程序中存在持有鎖時(shí)執(zhí)行一些無(wú)法結(jié)束的結(jié)構(gòu)(無(wú)限循環(huán)或者是等待某個(gè)資源發(fā)生阻塞),那么很可能將導(dǎo)致饑餓,因?yàn)槠渌枰@個(gè)鎖的線程將無(wú)法得到它。
活鎖:活鎖不會(huì)阻塞線程,但也不能繼續(xù)執(zhí)行。假如程序不能正確的執(zhí)行某個(gè)操作,因?yàn)槭聞?wù)回滾,并將其放到隊(duì)列的頭部。由于這條事務(wù)回滾的消息被放回到隊(duì)列頭部,處理器將反復(fù)調(diào)用,并返回相同的結(jié)果。
性能問(wèn)題
性能問(wèn)題和活躍性問(wèn)題是密切相關(guān)的?;钴S性意味著某件正確的事情最終會(huì)發(fā)生,但是我們一般更希望正確的事情盡快的發(fā)生。性能問(wèn)題包括多個(gè)方面:服務(wù)時(shí)間過(guò)長(zhǎng)、響應(yīng)不靈敏、吞吐率過(guò)低、資源消耗過(guò)高、和可伸縮性較差等。在多線程程序中,,還存在由于使用多線程而引入的其他問(wèn)題。在多線程程序中,當(dāng)線程調(diào)度器臨時(shí)掛起活躍線程并轉(zhuǎn)而運(yùn)行另一個(gè)線程時(shí),就會(huì)頻繁的出現(xiàn)上下文切換操作,這種操作將帶來(lái)極大的開銷(保存和恢復(fù)執(zhí)行上下文,丟失局部性,并且CPU時(shí)鐘周期將更多地花費(fèi)在線程調(diào)度上而不是線程運(yùn)行上)。并且,當(dāng)多個(gè)線程共享數(shù)據(jù)時(shí),必須使用同步機(jī)制,而這些機(jī)制往往會(huì)抑制某些編譯器優(yōu)化,使內(nèi)存緩沖區(qū)的數(shù)據(jù)無(wú)效,以及增加共享內(nèi)存總線的同步流量。
什么是線程安全性?
當(dāng)多個(gè)線程訪問(wèn)某個(gè)類時(shí),不管運(yùn)行時(shí)環(huán)境采用何種調(diào)度方式或者這些線程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個(gè)類都能表現(xiàn)出正確的行為,那么就稱這個(gè)類是線程安全的。
從ThreadSafeTest例子我們可以清楚線程安全性可能是非常復(fù)雜的,再?zèng)]有充足同步的情況下,多個(gè)線程中的操作執(zhí)行順序是不可預(yù)測(cè)的,可能會(huì)發(fā)生奇怪的結(jié)果。
無(wú)狀態(tài)對(duì)象一定是線程安全的**
相信大家都對(duì)servlet有過(guò)了解,它是一個(gè)框架,其作用大概就是接收請(qǐng)求,處理參數(shù),分發(fā)請(qǐng)求和返回結(jié)果。servlet是線程安全的,因?yàn)樗菬o(wú)狀態(tài)的。我們來(lái)自定義個(gè)servlet:
public class NoStateCalculate implements Servlet { @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { //解析數(shù)據(jù)樣本,計(jì)算結(jié)果calculate BigInteger calculate = calculate(req); Integer data= getData(calculate); res.getOutputStream().print(data); } }
這個(gè)servlet從請(qǐng)求中提取參數(shù)并計(jì)算結(jié)果calculate,然后去數(shù)據(jù)庫(kù)中查詢對(duì)應(yīng)的數(shù)據(jù)data,最終將其寫入到輸出中。它是一個(gè)無(wú)狀態(tài)的類,它不包含任何域,也不包含其他任何域的引用,所有的臨時(shí)狀態(tài)都存在于線程棧上的局部變量表中,并且只能由正在執(zhí)行的線程訪問(wèn),線程之間不會(huì)相互影響,因此可以說(shuō)線程之間沒有共享狀態(tài)。由于多線程訪問(wèn)無(wú)狀態(tài)對(duì)象的行為不會(huì)影響到其他線程中操作的正確性,因此無(wú)狀態(tài)對(duì)象一定是線程安全的。
原子性
ThreadSafeTest例子并不是一個(gè)線程安全的例子,原因是將有100個(gè)線程同時(shí)調(diào)用count++,而count++又不是一個(gè)原子性的操作,其結(jié)果將可能是不正確的。
競(jìng)態(tài)條件:當(dāng)某個(gè)計(jì)算的正確性取決于多個(gè)線程的交替執(zhí)行時(shí)序時(shí),那么就會(huì)發(fā)生競(jìng)態(tài)條件(正確的結(jié)果依賴于運(yùn)氣)。
復(fù)合操作:count++就是一組復(fù)合操作,要避免競(jìng)態(tài)條件問(wèn)題,就必須將操作原子化。
讓我們對(duì)ThreadSafeTest進(jìn)行改造,使用jdk提供的原子變量類AtomicInteger:
public class ThreadSafeTest { static AtomicInteger count =new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { final CountDownLatch countDownLatch = new CountDownLatch(1); for (int i=0;i<100;i++){ new Thread(new Runnable() { @Override public void run() { try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } for (int x=0;x<100;x++){ count.incrementAndGet(); } } }).start(); } countDownLatch.countDown(); Thread.sleep(5000); System.out.println("count:"+count); } }
通過(guò)AtomicInteger可以將單個(gè)整數(shù)變量的操作原子化,使其變成線程安全的。當(dāng)共享狀態(tài)變量多于一個(gè)時(shí),這種機(jī)制就不能解決問(wèn)題了,應(yīng)該通過(guò)鎖機(jī)制保證操作的原子性。
加鎖機(jī)制
內(nèi)置鎖:java提供了synchronized內(nèi)置鎖來(lái)支持原子性。它可以修飾在方法上,代碼塊上,其中同步代碼塊和普通方法的鎖就是方法調(diào)用所在的對(duì)象,而靜態(tài)方法的synchronized則以當(dāng)前的Class對(duì)象作為鎖。線程在進(jìn)入同步代碼之前會(huì)自動(dòng)獲得鎖,退出同步代碼塊時(shí)自動(dòng)釋放鎖。synchronized同時(shí)也是一種互斥鎖,最多只有一個(gè)線程持有這種鎖,所以它可以保證同步代碼操作的原子性。
重入鎖:當(dāng)某個(gè)線程請(qǐng)求一個(gè)由其他線程持有的鎖時(shí),發(fā)出的請(qǐng)求就會(huì)阻塞。然而內(nèi)置鎖是可重入的,因此如果某個(gè)線程試圖獲得一個(gè)已經(jīng)由他自己的持有的鎖,那么這個(gè)請(qǐng)求就會(huì)成功。"重入"意味著獲取鎖的操作的粒度是線程,重入的一種實(shí)現(xiàn)方式就是為每個(gè)鎖關(guān)聯(lián)一個(gè)獲取計(jì)數(shù)值和一個(gè)所有者線程。當(dāng)數(shù)值為0時(shí)就代表鎖沒有被任何線程獲得,當(dāng)一個(gè)線程獲得鎖時(shí),計(jì)數(shù)器會(huì)加1,當(dāng)同一個(gè)線程再次獲得鎖時(shí),將會(huì)在加1,以此來(lái)實(shí)現(xiàn)鎖的重入功能。
用鎖來(lái)保護(hù)狀態(tài):
活躍性和性能
java的語(yǔ)法糖提供了一個(gè)內(nèi)置鎖synchronized,它可以很好地保證同步代碼塊只有一個(gè)線程執(zhí)行。但是如果synchronized使用的不當(dāng),將會(huì)帶來(lái)嚴(yán)重的活躍性和性能問(wèn)題。其對(duì)應(yīng)的優(yōu)化技術(shù)有很多,避免死鎖,減小鎖粒度,鎖分段等等都可有效的解決活躍性問(wèn)題和性能問(wèn)題(留到以后再介紹)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/69620.html
摘要:進(jìn)程可創(chuàng)建多個(gè)線程來(lái)執(zhí)行同一程序的不同部分。就緒等待線程調(diào)度。運(yùn)行線程正常運(yùn)行阻塞暫停運(yùn)行,解除阻塞后進(jìn)入狀態(tài)重新等待調(diào)度。消亡線程方法執(zhí)行完畢返回或者異常終止。多線程多的情況下,依次執(zhí)行各線程的方法,前頭一個(gè)結(jié)束了才能執(zhí)行后面一個(gè)。 淺談Python多線程 作者簡(jiǎn)介: 姓名:黃志成(小黃)博客: 博客 線程 一.什么是線程? 操作系統(tǒng)原理相關(guān)的書,基本都會(huì)提到一句很經(jīng)典的話: 進(jìn)程...
摘要:線程池的作用降低資源消耗。通過(guò)重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的資源浪費(fèi)。而高位的部分,位表示線程池的狀態(tài)。當(dāng)線程池中的線程數(shù)達(dá)到后,就會(huì)把到達(dá)的任務(wù)放到中去線程池的最大長(zhǎng)度。默認(rèn)情況下,只有當(dāng)線程池中的線程數(shù)大于時(shí),才起作用。 線程池的作用 降低資源消耗。通過(guò)重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的資源浪費(fèi)。 提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),不需要等到線程創(chuàng)建就能立即執(zhí)行...
摘要:比如需要用多線程或分布式集群統(tǒng)計(jì)一堆用戶的相關(guān)統(tǒng)計(jì)值,由于用戶的統(tǒng)計(jì)值是共享數(shù)據(jù),因此需要保證線程安全。如果類是無(wú)狀態(tài)的,那它永遠(yuǎn)是線程安全的。參考探索并發(fā)編程二寫線程安全的代碼 線程安全類 保證類線程安全的措施: 不共享線程間的變量; 設(shè)置屬性變量為不可變變量; 每個(gè)共享的可變變量都使用一個(gè)確定的鎖保護(hù); 保證線程安全的思路: 1. 通過(guò)架構(gòu)設(shè)計(jì) 通過(guò)上層的架構(gòu)設(shè)計(jì)和業(yè)務(wù)分析來(lái)避...
閱讀 3416·2023-04-26 02:41
閱讀 2476·2023-04-26 00:14
閱讀 2909·2021-08-11 10:22
閱讀 1298·2019-12-27 11:38
閱讀 3586·2019-08-29 18:34
閱讀 2392·2019-08-29 12:13
閱讀 2967·2019-08-26 18:26
閱讀 1881·2019-08-26 16:49