什么是線程安全壤巷?
線程安全胧华,有兩個(gè)重要的特征說明:“共享”和“可變”宙彪。
共享是指可以被多個(gè)線程同時(shí)訪問释漆;
可變是指變量的值在生命周期內(nèi)是可以變化的;
如何實(shí)現(xiàn)線程安全
一個(gè)對象是否需要線程安全的檀训,取決于它是否被多個(gè)線程訪問享言;
如何保證一個(gè)對象的線程安全览露,則需要采用同步機(jī)制來協(xié)同對對象可變狀態(tài)的訪問;
給線程安全下一個(gè)明確的定義:當(dāng)多個(gè)線程訪問這個(gè)對象或者資源時(shí)命锄,如果這個(gè)對象或資源始終都能表現(xiàn)出數(shù)據(jù)的一致性的狀態(tài)脐恩,那么就稱這個(gè)對象或者資源是線程安全的侦讨;
數(shù)據(jù)資源的有無狀態(tài)化
無狀態(tài)的對象一定是線程安全的。
有狀態(tài)的對象崇猫,多線程環(huán)境下诅炉,多個(gè)線程共享資源,且進(jìn)行的不是原子性操作涕烧,這個(gè)時(shí)候就要考慮線程的安全控制問題
比如:count++澈魄,其實(shí)是不具備原子性的痹扇,因?yàn)檫@個(gè)步驟實(shí)際會(huì)被拆分為三個(gè)步驟鲫构,即 讀取结笨、修改和寫入炕吸,而這三個(gè)步驟有可能在某個(gè)時(shí)刻因CPU時(shí)間片的切換問題勉痴,而只執(zhí)行其中一兩個(gè)步驟蒸矛,這就不具備原子性。
原子化能力支持
在Java中斩祭,為了解決這個(gè)問題摧玫,java.util.concurrent.atomic包提供了很多的類蚊伞,來保證數(shù)據(jù)操作的原子性时迫,比如我們之前的程序可以修改為
基本數(shù)據(jù)類型 AtomicInteger
數(shù)組類型 AtomicIntegerArray
AtomicInteger integer = new AtomicInteger(0);
integer.incrementAndGet()
內(nèi)部的原理是采用了CAS機(jī)制
那么什么是CAS機(jī)制?
CAS有人翻譯為Compare And Set或Compare And Swap都是正確的掠拳。
多線程并發(fā)執(zhí)行的狀態(tài)下溺欧,鎖的狀態(tài)改變,基本都是使用CAS原理绑改,它有一個(gè)比較別扭的叫法“CPU硬件同步原語”座哩,算法是基于CPU硬件的,原子性操作柏靶,不會(huì)被其他線程打斷弃理。
CAS的算法,比較當(dāng)前值和期望的值是否相等屎蜓,如果相等痘昌,則將當(dāng)前值賦予一個(gè)新值。
再比如修改一個(gè)Boolean的類型的變量的值炬转,我們也可以采用
private AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public void lock(){
//期望是false辆苔,如果是false,則可以修改為true
atomicBoolean.compareAndSet(false, true);
}
同步鎖機(jī)制支持
只要程序中存在“先判斷街佑,再更新”磁携,那么就要保證這兩個(gè)操作在一個(gè)原子操作里面歪脏,才能保證線程安全豪硅。
public synchronized int getCount(){
return count++;
}
Java鎖機(jī)制的一些特點(diǎn)
監(jiān)視鎖砚著、互斥鎖、可重入鎖都是在這個(gè)鎖的特點(diǎn)。
監(jiān)視鎖:java的每一個(gè)對象都可以用來做監(jiān)視鎖,也就是為什么我們的wait、notify方法定義在Object類的原因。
互斥鎖:表示最多只有一個(gè)線程可以持有這把鎖。
可重入鎖:是指當(dāng)線程A請求一個(gè)由線程B持有的鎖時(shí),線程B會(huì)進(jìn)入阻塞狀態(tài)账千;而如果線程A如果再訪問另一段代碼桦山,而這個(gè)代碼的鎖是已經(jīng)被線程A持有的,這個(gè)時(shí)候請求是可以成功的钉凌,這就叫可重入酸纲。
Java鎖機(jī)制的簡單原理
JVM為每個(gè)鎖設(shè)置兩個(gè)屬性疾嗅,獲取計(jì)數(shù)值和所有者線程论悴,當(dāng)計(jì)數(shù)值為0時(shí)玖像,這個(gè)鎖就被認(rèn)為是沒有被任何線程持有,當(dāng)線程請求一個(gè)未被持有的鎖時(shí)乡洼,JVM將記錄鎖的持有者,并且計(jì)數(shù)值+1巴比。
如果同一個(gè)線程再次獲取這個(gè)鎖佣耐,則計(jì)數(shù)值將遞增,而當(dāng)線程退出同步代碼塊時(shí)奸远,計(jì)數(shù)器會(huì)相應(yīng)遞減掖鱼,當(dāng)計(jì)數(shù)值為0戏挡,這個(gè)鎖將被釋放。
活躍性問題
承接上面解決安全性的問題分析,鎖機(jī)制會(huì)存在活躍性問題持际,比如:死鎖,饑餓闷祥,活鎖座泳,這些都是屬于活躍性問題。
死鎖
多個(gè)線程幕与,各自占對方的資源挑势,都不愿意釋放,從而造成死鎖啦鸣,A線程需要等待的鎖被B線程占用潮饱,而B線程需要的等待的鎖被A線程占用,所以相互都不釋放诫给,于是就陷入了死鎖香拉。
饑餓
多個(gè)線程訪問同一個(gè)同步資源,有些線程總是沒有機(jī)會(huì)得到互斥鎖中狂,這種就叫做饑餓凫碌。
出現(xiàn)饑餓的三種情況
-
高優(yōu)先級(jí)的線程吞噬了低優(yōu)先級(jí)的線程的CPU時(shí)間片
理論上來說,線程優(yōu)先級(jí)高的線程會(huì)比線程優(yōu)先級(jí)低的線程獲得更多的執(zhí)行機(jī)會(huì)胃榕,但是java的線程優(yōu)先級(jí)不是絕對出現(xiàn)這樣的效果盛险。
一般而言:優(yōu)先級(jí)高的出現(xiàn)頻率會(huì)比優(yōu)先級(jí)低的高很多
不同的操作系統(tǒng)對線程的優(yōu)先級(jí)支持是不同的,規(guī)定是在1-10之間,java通過3個(gè)常量來屏蔽這種操作系統(tǒng)的底層差異化苦掘。
線程被永久阻塞在等待進(jìn)入同步代碼塊的狀態(tài)
等待的線程永遠(yuǎn)不被喚醒
建議大家采用公平鎖來代替synchronized這種互斥鎖
活鎖
兩個(gè)人在走廊上碰見泉褐,大家都互相很有禮貌,互相禮讓鸟蜡,A從左到右膜赃,B也從從左轉(zhuǎn)向右,發(fā)現(xiàn)又擋住了地方揉忘,繼續(xù)轉(zhuǎn)換方向跳座,但又碰到了,反反復(fù)復(fù)泣矛,一直沒有機(jī)會(huì)運(yùn)行下去疲眷。