JAVA基礎知識系列---進程、線程安全

1.1 臨界區

保證在某一時刻只有一個線程能訪問數據的簡便方法,在任意時刻只容許一個線程對資源進行訪問。若是有多個線程試圖同時訪問臨界區,那麼在有一個線程進入後,其餘全部試圖訪問臨界區的線程將被掛起,並一直持續到進入臨界區的線程離開。臨界區在被釋放後,其餘線程能夠繼續搶佔,並以此達到用原子方式操做共享資源的目的java

1.2 互斥量

互斥量和臨界區很類似,只能擁有互斥對象的線程才能具備訪問資源的權限,因爲互斥對象只有一個,所以就決定了任何狀況下次共享資源都不會同時被多個線程所訪問。當前佔據資源的線程在任務處理完後應將擁有的互斥對象交出,以便其餘線程在得到後能夠訪問資源。互斥量比臨界區複雜,由於使用互斥不只僅可以在同一應用程序不一樣線程中實現資源的安全共享,並且能夠在不一樣應用程序的線程之間實現對資源的安全共享。數組

1.3 管程/信號量

管程和信號量是同一個概念。指一個互斥獨佔鎖定的對象或稱爲互斥體。在給定的時間,僅有一個線程能夠得到管程。當一個線程須要鎖定,他必須進入管程。全部其餘的試圖進入已經鎖定的管程的線程必須掛起直到第一個線程退出管程。這些其餘的線程被稱爲等待線程。一個擁有管程的線程若是願意的話能夠再次進入相同的管程(可重入性)緩存

1.4 CAS操做

CAS操做(compare and swap)CAS有3個操做數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改成B,不然返回V。這是一種樂觀鎖的思路,它相信在它修改以前,沒有其它線程去修改它;而Synchronized是一種悲觀鎖,它認爲在它修改以前,必定會有其它線程去修改它,悲觀鎖效率很低。下面來看一下AtomicInteger是如何利用CAS實現原子性操做的。安全

1.5 重排序

編譯器和處理器爲了提升性能,而在程序執行時會對程序進行重排序。他的出現是爲了提升程序的併發度。從而提升性能;可是對於多線程程序,重排序可能會致使程序執行的結果不是咱們須要的結果,重排序分爲編譯器和處理器倆個方面。而處理器重排序包括指令級重排序和內存重排序。多線程

小節

在java中,全部的變量(實例字段,靜態字段,構成數組的元素,不包括局部變量和方法參數)都存儲在主內存中,內個線程都有本身的工做內存,線程的工做內存保存被線程使用到的變量的主內存副本拷貝。線程對變量的全部操做都必須在工做內存中進行,爲不能直接讀寫主內存的變量。不一樣線程之間也不恩可以直接訪問對方工做內存中的變量,線程間比變量值的傳遞經過主內存來完成。併發

JAVA中線程安全相關關鍵字及類

主要包括:synchronized,Volitile,ThreadLocal,Lock,Condition性能

2.1 Volitile

做用:優化

1)保證了心智能當即存儲到主內存才,每次使用前當即從主內存中刷新this

2)禁止指令重排序優化操作系統

Volitile關鍵字不能保證在多線程環境下對共享數據的操做的正確性,可使用在本身狀態改變以後須要當即通知全部線程的狀況下,只保證可見性,不保證原子性。即經過刷新變量值確保可見性。

Java中synchronized和final也能保證可見性

synchronized:同步快經過變量鎖定前必須清空工做內存中的變量值,從新從主內存中讀取變量值,解鎖前必須把變量值同步回主內存來確保可見性。

final:被final修飾的字段在構造器中一旦被初始化完成,而且構造器沒有把this引用傳遞進去,那麼在其餘線程中就能看見final字段的值,無需同步就能夠被其餘線程正確訪問。

2.2 synchronized

把代碼塊聲明爲synchronized,有倆個做用,一般是指改代碼具備原子性和可見性。若是沒有同步機制提供的這種可見性,線程看到的共享比那裏多是修改前的值或不一致的值,這將引起許多嚴重問題。

原理:當對象獲取鎖是,他首先是本身的高速緩存無效,這樣就能夠保證直接從主內存中裝入變量,一樣在對象釋放鎖以前,他會刷新其高速緩存,強制使已作的任何更改都出如今主內存中,這樣會保證在同一個鎖上同步的倆個線程看到在synchronized塊內修改的變量的相同值。

synchronized釋放由JVM本身管理。

存在的問題:

1)沒法中斷一個正在等待得到鎖的線程

2)沒法經過投票獲得鎖,若是不想等待下去,也就無法獲得鎖

3)同步還須要鎖的釋放只能在與得到鎖所在的堆棧幀相同的堆棧中進行,多數狀況下,這沒問題(並且與一場處理交互的很好),可是,確實存在一些非塊結構的鎖定更適合狀況。

2.3 Lock

Lock是有JAVA編寫而成的,在java這個層面是無關JVM實現的。包括:ReentrantLock,ReadWriteLock。其本質都依賴於AbstractQueueSynchronized類。Lock提供了不少鎖的方式,嘗試鎖,中斷鎖等。釋放鎖的過程由JAVA開發人員本身管理。

就性能而言,對於資源衝突很少的狀況下synchronized更加合理,但若是資源訪問衝突多的狀況下,synchronized的性能會快速降低,而Lock能夠保持平衡。

2.4 condition

Condition將Object監視器方法(wait,notify,notifyall)分解成大相徑庭的對象,以便經過這些對象與任意Lock實現組合使用,爲每一個對象提供多個等待set(wait-set),,其中Lock替代了synchronized方法和語句的使用,condition替代了Object監視器方法的使用。Condition實例實質上被你綁定到一個鎖上。要爲特定Lock實例得到Condition實例,請使用其newCondition()方法。

2.5 ThreadLock

線程局部變量。

變量是同一個,可是每一個線程都使用同一個初始值,也就是使用同一個變量的一個新的副本,這種狀況下TreadLocal就很是有用。

應用場景:當不少線程須要屢次使用同一個對象,而且須要該對象具備相同初始值的時候,最適合使用TreadLocal。

事實上,從本質上講,就是每一個線程都維持一個MAP,而這個map的key就是TreadLocal,而值就是咱們set的那個值,每次線程在get的時候,都從本身的變量中取值,既然從本身的變量中取值,那就確定不存在線程安全的問題。整體來說,TreadLocal這個變量的狀態根本沒有發生變化。它僅僅是充當了一個key的角色,另外提供給每個線程一個初始值。若是容許的話,咱們本身就能實現一個這樣的功能,只不過剛好JDK就已經幫助咱們作了這個事情。

使用TreadLocal維護變量時,TreadLocal爲每一個使用該變量的線程提供獨立地變量副本,因此每個線程均可以獨立地改變本身的副本,而不會英語其餘線程所對應的副本。從線程的角度看,目標變量對象是線程的本地變量,這也是類名中Local所須要表達的意思。

TreadLocal的四個方法:

void set(Object val),設置當前線程的線程局部變量的值

Object get()返回當前線程所對用的線程局部變量。

void remove() 將當前線程局部變量的值刪除,目的是爲了減小內存的佔用,線程結束後,局部變量自動被GC

Object initValue() 返回該線程局部變量的初始值,使用protected修飾,顯然是爲了讓子類覆蓋而設計的。

線程安全的實現方式

3.1 互斥同步

在多線程訪問的時候,保證同一時間只有一條線程使用。

臨界區,互斥量,管程都是同步的一種手段。

java中最基本的互斥同步手段是synchronized,編譯以後會造成monitorenter和monitorexit這倆個字節碼指令,這倆個字節碼都須要一個reference類型的參數來指明要鎖定和解鎖的對象,還有一個鎖的計數器,來記錄加鎖的次數,加鎖幾回就要一樣解鎖幾回才能恢復到無鎖狀態。

java的線程是映射到操做系統的原生線程之上的,無論阻塞仍是喚醒都須要操做系統的幫助完成,都須要從用戶態轉換到核心態,這是很耗費時間的,是java語言中的一個重量級的操做,雖然虛擬機自己會作一點優化的操做,好比通知操做系統阻塞以前會加一段自旋等待的過程,避免頻繁切換到核心態。

3.2 非阻塞同步

互斥和同步最主要的問題就是阻塞和喚醒所帶來的性能的問題,因此這一般叫阻塞同步(悲觀的併發策略).隨着硬件指令集的發展,咱們有另外的選擇:基於衝突檢測的樂觀併發策略,通俗講就是先操做,若是沒有其餘線程爭用共享的數據,操做就成功,若是有,則進行其餘的補償(最多見的就是不斷的重試)。這種樂觀的併發策略許多實現都不須要把線程先掛起,這種同步操做被稱爲非阻塞同步。

3.3 無同步

部分代碼天生就是線程安全的,不須要同步。

1)可重入代碼:純代碼,具備不依賴存儲在堆上的數據和公用的系統資源,用到的狀態量都由參數中傳入,不調用非可重入的方法等特徵,它的返回結果是能夠預測的。

2)線程本地存儲:把共享數據的可見性範圍限制在同一個線程以內,這樣就無需同步也能保證線程之間不出現數據爭用問題。能夠經過java.lang.TreadLocal類來實現線程本地存儲的功能。

相關文章
相關標籤/搜索