JAVA基础知识系列---进程、线程安全
1.1 臨界區
保證在某一時刻只有一個線程能訪問數據的簡便方法,在任意時刻只允許一個線程對資源進行訪問。如果有多個線程試圖同時訪問臨界區,那么在有一個線程進入后,其他所有試圖訪問臨界區的線程將被掛起,并一直持續到進入臨界區的線程離開。臨界區在被釋放后,其他線程可以繼續搶占,并以此達到用原子方式操作共享資源的目的
1.2 互斥量
互斥量和臨界區很相似,只能擁有互斥對象的線程才能具有訪問資源的權限,由于互斥對象只有一個,因此就決定了任何情況下次共享資源都不會同時被多個線程所訪問。當前占據資源的線程在任務處理完后應將擁有的互斥對象交出,以便其他線程在獲得后可以訪問資源?;コ饬勘扰R界區復雜,因為使用互斥不僅僅能夠在同一應用程序不同線程中實現資源的安全共享,而且可以在不同應用程序的線程之間實現對資源的安全共享。
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)保證了心智能立即存儲到主內存才,每次使用前立即從主內存中刷新
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的時候,都從自己的變量中取值,既然從自己的變量中取值,那就肯定不存在線程安全的問題??傮w來講,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類來實現線程本地存儲的功能。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的JAVA基础知识系列---进程、线程安全的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 45. ExtJS ComboBox 下
- 下一篇: openfaas cli 安装