Java 高效併發編程初探

在學習Java的時候,多線程是一個很重要很重要的問題。
從Java的內存模型而言,多線程需要讀取內存,CPU(一個CPU代表一個線程)和內存的讀取的速度不是一個等級的,那麼就需要一個高效的緩存線程,通過將內存上需要操作的數據複製到緩存線程上,再由緩存線程與線程進行通訊,然後緩存線程將結果返回到內存中。
但是在多線程併發操作的時候,往往帶來的問題就是線程的不安全,資源的不安全,所以線程的安全非常重要。
線程安全:
(非官方,僅個人理解):當多個線程同時訪問同一塊內存,或者同一個對象的時候,無需考慮這些線程在當前的運行環境下的調度和同時執行,也無需進行額外的同步而可以得到正確的結果,那麼就說這個對象或者內存是線程安全的。
Java中的線程安全等級分佈:
按照我的理解而言,Java中的線程安全可以分爲3種,分別是絕對安全、相對安全、不安全三個等級。
絕對安全:
如果被final修飾的共享數據,是不會被修改的,因爲final態爲終態。如果final修飾的共享數據是對象的話,那麼就需要保證fianl對自己的狀態不會產生任何的影響,例如String的subString()方法返回的是一個新的子串對象,而自己卻不會受到影響。
另外的情況是如果類中的每個方法都加同步synchronoized關鍵字修飾的話,那麼這個類可以說是完全同步的,比如Vector。

相對安全:
相對安全是對於絕對安全而言的,例如ArrayList與Vector,StringBuilder與StringBuffer之間的關係,前者是無鎖的,後者是有鎖的,但是一般情況下,使用前者的效率更高而且出現不安全的機率是非常小的,如果真正會出現線程安全的問題,那麼只需要在局部加鎖就可以了。

不安全:
指的是多條線程訪問同一塊共享數據的時候會發生資源矛盾,出錯的現象。典型的例如多線程賣票問題。

線程安全的實現方法:
1、同步 synchronized
非常常用,實現過程是一個當加上synchronized後,經過編譯,會在同步塊的前後生成一個monitorenter和moniterexit,當一個線程需要執行同步塊的時候,會嘗試獲取對象的鎖,如果對象沒有加鎖或者是線程已經獲取得到對象的鎖的時候,線程的持有鎖會加1,然後如果執行moniterexit的時候,鎖會減1,當鎖爲0的時候,該對象的鎖被釋放,如果此時另外線程得到鎖,那麼其他線程就繼續阻塞式等待。

2、使用lock
lock也就是鎖,lock與synchronized的區別就是多了幾項高級技能,另外在多線程併發執行的時候效率更高。

第一項高級技能:等待可中斷
也就是說當其他線程正在執行代碼邏輯的時候,其他線程必須等待佔有鎖的線程完成操作,但是有的時候等待久了,或者說線程完全在裏面沉睡的時候,等待時間過長,就可以放棄等待,去做自己的事情。

第二項高級技能:公平鎖
在synchronized中,靠的是競爭,也就是說哪一個CPU搶到執行權,那麼對應的線程可以去執行同步快的邏輯,但是lock可以開公平鎖,也就是根據申請鎖的時間進行獲得鎖。

另外,在synchronized中,代碼效率不高,而lock效率會更高一點,特別是在JDK1.6之前。

多線程的缺陷:
在Java中,線程的執行是依靠我們操作系統的內核態調用原生線程進行操作的,那麼這個過程是非常耗費時間的,特別是需要從內核喚醒操作系統原生線程的時候,所以多線程的的阻塞機制的是很消耗時間的。

怎麼辦?
鎖的優化機制!!
1、自旋鎖
之前說線程的等待是阻塞式的,在這種情況下,線程就像沉睡了一樣,等到其他線程喚醒的時候,需要從操作系統的內核態中喚醒,那麼就消耗很多的時間,所以如果不讓線程沉睡,而讓線程自旋(類似於loading…..),然後只要控制自旋不要太多次,那麼就可以減少時間的損耗,但是會帶來資源的浪費,因爲線程一直在執行。
通過這一個方式可以減少線程掛起和線程恢復的時間。

2、自適應自旋鎖
前面說的自旋鎖是指人爲控制自旋的次數,自適應是根據前面的情況去確定自旋的次數,例如前面自旋30次就可以成功獲得鎖,那麼自適應鎖可以嘗試着30次,如果不行就在掛起線程的操作。類似於擔保機制,冒險的方法,但是也顯得虛擬機更加智能。

3、鎖消除
鎖消除是指虛擬機在編譯的時候,會檢測是否真的需要這個鎖,如果不需要的話會直接消除這個鎖。

4、鎖粗化
如果有一段代碼,層層嵌套,每一層都進行同步,那麼虛擬機將會去除這些鎖,在最外層加鎖,這樣虛擬機的加鎖就變得更加智能。

okay,暫時先更新到這裏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章