本文章的知識(shí)主要參考《Java并發(fā)編程實(shí)戰(zhàn)》這本書的前4章,這本書的前4章都是講解并發(fā)的基礎(chǔ)的。要是能好好理解這些基礎(chǔ),那么我們往后的學(xué)習(xí)就會(huì)事半功倍。 當(dāng)然了,《Java并發(fā)編程實(shí)戰(zhàn)》可以說(shuō)是非常經(jīng)典的一本書。我是未能完全理解的,在這也僅僅是拋磚引玉。想要更加全面地理解我下面所說(shuō)的知識(shí)點(diǎn),可以去閱讀一下這本書,總的來(lái)說(shuō)還是不錯(cuò)的。 首先來(lái)預(yù)覽一下《Java并發(fā)編程實(shí)戰(zhàn)》前4章的目錄究竟在講什么吧: 第1章 簡(jiǎn)介
ps:這一部分我就不講了,主要是引出我們接下來(lái)的知識(shí)點(diǎn),有興趣的同學(xué)可翻看原書~ 第2章 線程安全性
第3章 對(duì)象的共享
第4章 對(duì)象的組合
那么接下來(lái)我們就開始吧~ 一、使用多線程遇到的問(wèn)題1.1線程安全問(wèn)題在前面的文章中已經(jīng)講解了線程【多線程三分鐘就可以入個(gè)門了!】,多線程主要是為了提高我們應(yīng)用程序的使用率。但同時(shí),這會(huì)給我們帶來(lái)很多安全問(wèn)題! 如果我們?cè)?strong>單線程中以“順序”(串行-->獨(dú)占)的方式執(zhí)行代碼是沒有任何問(wèn)題的。但是到了多線程的環(huán)境下(并行),如果沒有設(shè)計(jì)和控制得好,就會(huì)給我們帶來(lái)很多意想不到的狀況,也就是線程安全性問(wèn)題 因?yàn)樵诙嗑€程的環(huán)境下,線程是交替執(zhí)行的,一般他們會(huì)使用多個(gè)線程執(zhí)行相同的代碼。如果在此相同的代碼里邊有著共享的變量,或者一些組合操作,我們想要的正確結(jié)果就很容易出現(xiàn)了問(wèn)題 簡(jiǎn)單舉個(gè)例子:
public class UnsafeCountingServlet extends GenericServlet implements Servlet { 但是在多線程環(huán)境下跑起來(lái),它的count值計(jì)算就不對(duì)了! 首先,它共享了count這個(gè)變量,其次來(lái)說(shuō)
于是多線程執(zhí)行的時(shí)候很可能就會(huì)有這樣的情況:
如果說(shuō):當(dāng)多個(gè)線程訪問(wèn)某個(gè)類的時(shí)候,這個(gè)類始終能表現(xiàn)出正確的行為,那么這個(gè)類就是線程安全的! 有個(gè)原則:能使用JDK提供的線程安全機(jī)制,就使用JDK的。 當(dāng)然了,此部分其實(shí)是我們學(xué)習(xí)多線程最重要的環(huán)節(jié),這里我就不詳細(xì)說(shuō)了。這里只是一個(gè)總覽,這些知識(shí)點(diǎn)在后面的學(xué)習(xí)中都會(huì)遇到~~~ 1.3性能問(wèn)題使用多線程我們的目的就是為了提高應(yīng)用程序的使用率,但是如果多線程的代碼沒有好好設(shè)計(jì)的話,那未必會(huì)提高效率。反而降低了效率,甚至?xí)斐伤梨i! 就比如說(shuō)我們的Servlet,一個(gè)Servlet對(duì)象可以處理多個(gè)請(qǐng)求的,Servlet顯然是一個(gè)天然支持多線程的。 又以下面的例子來(lái)說(shuō)吧:
從上面我們已經(jīng)說(shuō)了,上面這個(gè)類是線程不安全的。最簡(jiǎn)單的方式:如果我們?cè)趕ervice方法上加上JDK為我們提供的內(nèi)置鎖synchronized,那么我們就可以實(shí)現(xiàn)線程安全了。 public class UnsafeCountingServlet extends GenericServlet implements Servlet { 雖然實(shí)現(xiàn)了線程安全了,但是這會(huì)帶來(lái)很嚴(yán)重的性能問(wèn)題:
這就導(dǎo)致了:我們完成一個(gè)小小的功能,使用了多線程的目的是想要提高效率,但現(xiàn)在沒有把握得當(dāng),卻帶來(lái)嚴(yán)重的性能問(wèn)題! 在使用多線程的時(shí)候:更嚴(yán)重的時(shí)候還有死鎖(程序就卡住不動(dòng)了)。 這些都是我們接下來(lái)要學(xué)習(xí)的地方:學(xué)習(xí)使用哪種同步機(jī)制來(lái)實(shí)現(xiàn)線程安全,并且性能是提高了而不是降低了~ 二、對(duì)象的發(fā)布與逸出書上是這樣定義發(fā)布和逸出的:
常見逸出的有下面幾種方式:
靜態(tài)域逸出: public修飾get方法: 方法參數(shù)傳遞我就不再演示了,因?yàn)榘褜?duì)象傳遞過(guò)去給另外的方法,已經(jīng)是逸出了~ 下面來(lái)看看該書給出this逸出的例子: 逸出就是本不應(yīng)該發(fā)布對(duì)象的地方,把對(duì)象發(fā)布了。導(dǎo)致我們的數(shù)據(jù)泄露出去了,這就造成了一個(gè)安全隱患!理解起來(lái)是不是簡(jiǎn)單了一丟丟? 2.1安全發(fā)布對(duì)象上面談到了好幾種逸出的情況,我們接下來(lái)來(lái)談?wù)?strong>如何安全發(fā)布對(duì)象。 安全發(fā)布對(duì)象有幾種常見的方式:
三、解決多線程遇到的問(wèn)題從上面我們就可以看到,使用多線程會(huì)把我們的系統(tǒng)搞得挺復(fù)雜的。是需要我們?nèi)ヌ幚砗芏嗍虑?,為了防止多線程給我們帶來(lái)的安全和性能的問(wèn)題~ 下面就來(lái)簡(jiǎn)單總結(jié)一下我們需要哪些知識(shí)點(diǎn)來(lái)解決多線程遇到的問(wèn)題。 3.1簡(jiǎn)述解決線程安全性的辦法使用多線程就一定要保證我們的線程是安全的,這是最重要的地方! 在Java中,我們一般會(huì)有下面這么幾種辦法來(lái)實(shí)現(xiàn)線程安全問(wèn)題:
3.2原子性和可見性何為原子性?何為可見性?當(dāng)初我在ConcurrentHashMap基于JDK1.8源碼剖析中已經(jīng)簡(jiǎn)單說(shuō)了一下了。不了解的同學(xué)可以進(jìn)去看看。 3.2.1原子性在多線程中很多時(shí)候都是因?yàn)槟硞€(gè)操作不是原子性的,使數(shù)據(jù)混亂出錯(cuò)。如果操作的數(shù)據(jù)是原子性的,那么就可以很大程度上避免了線程安全問(wèn)題了!
原子性就是執(zhí)行某一個(gè)操作是不可分割的, 也有人將其做成了表格來(lái)分類,我們來(lái)看看: 使用這些類相關(guān)的操作也可以進(jìn)他的博客去看看:
3.2.2可見性對(duì)于可見性,Java提供了一個(gè)關(guān)鍵字:volatile給我們使用~
volatile經(jīng)典總結(jié):volatile僅僅用來(lái)保證該變量對(duì)所有線程的可見性,但不保證原子性 我們將其拆開來(lái)解釋一下:
使用了volatile修飾的變量保證了三點(diǎn):
一般來(lái)說(shuō),volatile大多用于標(biāo)志位上(判斷操作),滿足下面的條件才應(yīng)該使用volatile修飾變量:
參考資料:
3.3線程封閉在多線程的環(huán)境下,只要我們不使用成員變量(不共享數(shù)據(jù)),那么就不會(huì)出現(xiàn)線程安全的問(wèn)題了。 就用我們熟悉的Servlet來(lái)舉例子,寫了那么多的Servlet,你見過(guò)我們說(shuō)要加鎖嗎??我們所有的數(shù)據(jù)都是在方法(棧封閉)上操作的,每個(gè)線程都擁有自己的變量,互不干擾! 在方法上操作,只要我們保證不要在棧(方法)上發(fā)布對(duì)象(每個(gè)變量的作用域僅僅停留在當(dāng)前的方法上),那么我們的線程就是安全的 在線程封閉上還有另一種方法,就是我之前寫過(guò)的:ThreadLocal就是這么簡(jiǎn)單 使用這個(gè)類的API就可以保證每個(gè)線程自己獨(dú)占一個(gè)變量。(詳情去讀上面的文章即可)~ 3.4不變性
上面我們共享的變量都是可變的,正由于是可變的才會(huì)出現(xiàn)線程安全問(wèn)題。如果該狀態(tài)是不可變的,那么隨便多個(gè)線程訪問(wèn)都是沒有問(wèn)題的! Java提供了final修飾符給我們使用,final的身影我們可能就見得比較多了,但值得說(shuō)明的是:
就好像下面這個(gè)HashMap,用final修飾了。但是它僅僅保證了該對(duì)象引用
不可變的對(duì)象引用在使用的時(shí)候還是需要加鎖的
要想將對(duì)象設(shè)計(jì)成不可變對(duì)象,那么要滿足下面三個(gè)條件:
String在我們學(xué)習(xí)的過(guò)程中我們就知道它是一個(gè)不可變對(duì)象,但是它沒有遵循第二點(diǎn)(對(duì)象所有的域都是final修飾的),因?yàn)镴VM在內(nèi)部做了優(yōu)化的。但是我們?nèi)绻且约涸O(shè)計(jì)不可變對(duì)象,是需要滿足三個(gè)條件的。 3.5線程安全性委托很多時(shí)候我們要實(shí)現(xiàn)線程安全未必就需要自己加鎖,自己來(lái)設(shè)計(jì)。 我們可以使用JDK給我們提供的對(duì)象來(lái)完成線程安全的設(shè)計(jì): 非常多的'工具類'供我們使用,這些在往后的學(xué)習(xí)中都會(huì)有所介紹的~~這里就不介紹了 四、最后正確使用多線程能夠提高我們應(yīng)用程序的效率,同時(shí)給我們會(huì)帶來(lái)非常多的問(wèn)題,這些都是我們?cè)谑褂枚嗑€程之前需要注意的地方。 無(wú)論是不變性、可見性、原子性、線程封閉、委托這些都是實(shí)現(xiàn)線程安全的一種手段。要合理地使用這些手段,我們的程序才可以更加健壯! 可以發(fā)現(xiàn)的是,上面在很多的地方說(shuō)到了:鎖。但我沒有介紹它,因?yàn)槲掖蛩?strong>留在下一篇來(lái)寫,敬請(qǐng)期待~~~ 書上前4章花了65頁(yè)來(lái)講解,而我只用了一篇文章來(lái)概括,這是遠(yuǎn)遠(yuǎn)不夠的,想要繼續(xù)深入的同學(xué)可以去閱讀書籍~ 之前在學(xué)習(xí)操作系統(tǒng)的時(shí)候根據(jù)《計(jì)算機(jī)操作系統(tǒng)-湯小丹》這本書也做了一點(diǎn)點(diǎn)筆記,都是比較淺顯的知識(shí)點(diǎn)?;蛟S對(duì)大家有幫助 |
|