不當心就鴿了幾天沒有更新了,這個星期回家咯。在學校的日子要努力一點才行!html
只有光頭才能變強java
回顧前面:編程
本文章的知識主要參考《Java併發編程實戰》這本書的前4章,這本書的前4章都是講解併發的基礎的。要是能好好理解這些基礎,那麼咱們日後的學習就會事半功倍。緩存
固然了,《Java併發編程實戰》能夠說是很是經典的一本書。我是未能徹底理解的,在這也僅僅是拋磚引玉。想要更加全面地理解我下面所說的知識點,能夠去閱讀一下這本書,總的來講仍是不錯的。安全
首先來預覽一下《Java併發編程實戰》前4章的目錄究竟在講什麼吧:微信
第1章 簡介多線程
ps:這一部分我就不講了,主要是引出咱們接下來的知識點,有興趣的同窗可翻看原書~併發
第2章 線程安全性異步
第3章 對象的共享工具
第4章 對象的組合
那麼接下來咱們就開始吧~
在前面的文章中已經講解了線程【多線程三分鐘就能夠入個門了!】,多線程主要是爲了提升咱們應用程序的使用率。但同時,這會給咱們帶來不少安全問題!
若是咱們在單線程中以「順序」(串行-->獨佔)的方式執行代碼是沒有任何問題的。可是到了多線程的環境下(並行),若是沒有設計和控制得好,就會給咱們帶來不少意想不到的情況,也就是線程安全性問題
由於在多線程的環境下,線程是交替執行的,通常他們會使用多個線程執行相同的代碼。若是在此相同的代碼裏邊有着共享的變量,或者一些組合操做,咱們想要的正確結果就很容易出現了問題
簡單舉個例子:
public class UnsafeCountingServlet extends GenericServlet implements Servlet { private long count = 0; public long getCount() { return count; } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { ++count; // To something else... } }
可是在多線程環境下跑起來,它的count值計算就不對了!
首先,它共享了count這個變量,其次來講++count;
這是一個組合的操做(注意,它並不是是原子性)
++count
實際上的操做是這樣子的:
因而多線程執行的時候極可能就會有這樣的狀況:
若是說:當多個線程訪問某個類的時候,這個類始終能表現出正確的行爲,那麼這個類就是線程安全的!
有個原則:能使用JDK提供的線程安全機制,就使用JDK的。
固然了,此部分實際上是咱們學習多線程最重要的環節,這裏我就不詳細說了。這裏只是一個總覽,這些知識點在後面的學習中都會遇到~~~
使用多線程咱們的目的就是爲了提升應用程序的使用率,可是若是多線程的代碼沒有好好設計的話,那未必會提升效率。反而下降了效率,甚至會形成死鎖!
就好比說咱們的Servlet,一個Servlet對象能夠處理多個請求的,Servlet顯然是一個自然支持多線程的。
又如下面的例子來講吧:
public class UnsafeCountingServlet extends GenericServlet implements Servlet { private long count = 0; public long getCount() { return count; } public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { ++count; // To something else... } }
從上面咱們已經說了,上面這個類是線程不安全的。最簡單的方式:若是咱們在service方法上加上JDK爲咱們提供的內置鎖synchronized,那麼咱們就能夠實現線程安全了。
public class UnsafeCountingServlet extends GenericServlet implements Servlet { private long count = 0; public long getCount() { return count; } public void synchronized service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { ++count; // To something else... } }
雖然實現了線程安全了,可是這會帶來很嚴重的性能問題:
這就致使了:咱們完成一個小小的功能,使用了多線程的目的是想要提升效率,但如今沒有把握得當,卻帶來嚴重的性能問題!
在使用多線程的時候:更嚴重的時候還有死鎖(程序就卡住不動了)。
這些都是咱們接下來要學習的地方:學習使用哪一種同步機制來實現線程安全,而且性能是提升了而不是下降了~
書上是這樣定義發佈和逸出的:
發佈(publish) 使對象可以在當前做用域以外的代碼中使用
逸出(escape) 當某個不該該發佈的對象被髮布了
常見逸出的有下面幾種方式:
靜態域逸出:
public修飾get方法:
方法參數傳遞我就再也不演示了,由於把對象傳遞過去給另外的方法,已是逸出了~
下面來看看該書給出this逸出的例子:
逸出就是本不該該發佈對象的地方,把對象發佈了。致使咱們的數據泄露出去了,這就形成了一個安全隱患!理解起來是否是簡單了一丟丟?
上面談到了好幾種逸出的狀況,咱們接下來來談談如何安全發佈對象。
安全發佈對象有幾種常見的方式:
public static Person = new Person()
;
從上面咱們就能夠看到,使用多線程會把咱們的系統搞得挺複雜的。是須要咱們去處理不少事情,爲了防止多線程給咱們帶來的安全和性能的問題~
下面就來簡單總結一下咱們須要哪些知識點來解決多線程遇到的問題。
使用多線程就必定要保證咱們的線程是安全的,這是最重要的地方!
在Java中,咱們通常會有下面這麼幾種辦法來實現線程安全問題:
count++
操做,可使用AtomicLong來實現原子性,那麼在增長的時候就不會出差錯了!)何爲原子性?何爲可見性?當初我在ConcurrentHashMap基於JDK1.8源碼剖析中已經簡單說了一下了。不瞭解的同窗能夠進去看看。
在多線程中不少時候都是由於某個操做不是原子性的,使數據混亂出錯。若是操做的數據是原子性的,那麼就能夠很大程度上避免了線程安全問題了!
count++
,先讀取,後自增,再賦值。若是該操做是原子性的,那麼就能夠說線程安全了(由於沒有中間的三部環節,一步到位【原子性】~原子性就是執行某一個操做是不可分割的, - 好比上面所說的count++
操做,它就不是一個原子性的操做,它是分紅了三個步驟的來實現這個操做的~ - JDK中有atomic包提供給咱們實現原子性操做~
也有人將其作成了表格來分類,咱們來看看:
使用這些類相關的操做也能夠進他的博客去看看:
對於可見性,Java提供了一個關鍵字:volatile給咱們使用~
volatile經典總結:volatile僅僅用來保證該變量對全部線程的可見性,但不保證原子性
咱們將其拆開來解釋一下:
使用了volatile修飾的變量保證了三點:
通常來講,volatile大多用於標誌位上(判斷操做),知足下面的條件才應該使用volatile修飾變量:
參考資料:
在多線程的環境下,只要咱們不使用成員變量(不共享數據),那麼就不會出現線程安全的問題了。
就用咱們熟悉的Servlet來舉例子,寫了那麼多的Servlet,你見過咱們說要加鎖嗎??咱們全部的數據都是在方法(棧封閉)上操做的,每一個線程都擁有本身的變量,互不干擾!
在方法上操做,只要咱們保證不要在棧(方法)上發佈對象(每一個變量的做用域僅僅停留在當前的方法上),那麼咱們的線程就是安全的
在線程封閉上還有另外一種方法,就是我以前寫過的:ThreadLocal就是這麼簡單
使用這個類的API就能夠保證每一個線程本身獨佔一個變量。(詳情去讀上面的文章便可)~
不可變對象必定線程安全的。
上面咱們共享的變量都是可變的,正因爲是可變的纔會出現線程安全問題。若是該狀態是不可變的,那麼隨便多個線程訪問都是沒有問題的!
Java提供了final修飾符給咱們使用,final的身影咱們可能就見得比較多了,但值得說明的是:
就好像下面這個HashMap,用final修飾了。可是它僅僅保證了該對象引用hashMap變量
所指向是不可變的,可是hashMap內部的數據是可變的,也就是說:能夠add,remove等等操做到集合中~~~
final HashMap<Person> hashMap = new HashMap<>();
不可變的對象引用在使用的時候仍是須要加鎖的
要想將對象設計成不可變對象,那麼要知足下面三個條件:
String在咱們學習的過程當中咱們就知道它是一個不可變對象,可是它沒有遵循第二點(對象全部的域都是final修飾的),由於JVM在內部作了優化的。可是咱們若是是要本身設計不可變對象,是須要知足三個條件的。
不少時候咱們要實現線程安全未必就須要本身加鎖,本身來設計。
咱們可使用JDK給咱們提供的對象來完成線程安全的設計:
很是多的"工具類"供咱們使用,這些在日後的學習中都會有所介紹的~~這裏就不介紹了
正確使用多線程可以提升咱們應用程序的效率,同時給咱們會帶來很是多的問題,這些都是咱們在使用多線程以前須要注意的地方。
不管是不變性、可見性、原子性、線程封閉、委託這些都是實現線程安全的一種手段。要合理地使用這些手段,咱們的程序才能夠更加健壯!
能夠發現的是,上面在不少的地方說到了:鎖。但我沒有介紹它,由於我打算留在下一篇來寫,敬請期待~~~
書上前4章花了65頁來說解,而我只用了一篇文章來歸納,這是遠遠不夠的,想要繼續深刻的同窗能夠去閱讀書籍~
以前在學習操做系統的時候根據《計算機操做系統-湯小丹》這本書也作了一點點筆記,都是比較淺顯的知識點。或許對你們有幫助
參考資料:
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠關注微信公衆號:Java3y。謝謝支持了!但願能多介紹給其餘有須要的朋友
文章的目錄導航: