1.JMM(java內(nèi)存模型)
Java內(nèi)存模型(Java Memory Model)描述了Java程序中各種變量(線程共享變量)的訪問規(guī)則,以及在JVM中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中讀取出變量這樣的底層細(xì)節(jié)。
多個(gè)線程同時(shí)對(duì)主內(nèi)存的一個(gè)共享變量進(jìn)行讀取和修改時(shí),首先會(huì)讀取這個(gè)變量到自己的工作內(nèi)存中成為一個(gè)副本,對(duì)這個(gè)副本進(jìn)行改動(dòng)之后,再更新回主內(nèi)存中變量。
(由于CPU時(shí)間片是以線程為最小單位,這里的工作內(nèi)存實(shí)際上就是指的物理緩存,CPU運(yùn)算時(shí)獲取數(shù)據(jù)的地方;而主內(nèi)存也就是指的是內(nèi)存,也就是原始的共享變量存放的位置)
JMM 關(guān)鍵技術(shù)點(diǎn)都是圍繞多線程的原子性、可見性、有序性來建立的。
原子性(Atomicity)
原子性是指一個(gè)原子操作在cpu中不可以暫停然后再調(diào)度。要么執(zhí)行完成,要么不執(zhí)行。
x++(包含三個(gè)原子操作)
a.將變量x 值取出放在寄存器中
b.將將寄存器中的值+1
c.將寄存器中的值賦值給x
可見性(Visibility)
如果一個(gè)線程對(duì)成員變量的修改,能夠及時(shí)的被其他線程看到,叫做成員變量的可見性。
禁止編譯器對(duì)成員變量進(jìn)行優(yōu)化,每次線程訪問成員變量時(shí),都強(qiáng)迫從內(nèi)存中重讀該成員變量的值;而且,當(dāng)成員變量發(fā)生變化時(shí),強(qiáng)迫線程將變化值回寫到共享內(nèi)存。
有序性(Ordering)
有序性問題的原因是因?yàn)槌绦蛟趫?zhí)行時(shí),可能會(huì)進(jìn)行指令重排,重排后的指令與原指令的順序未必一致。
本線程內(nèi)觀察,所有操作都是有序的;如果在一個(gè)線程中觀察另一個(gè)線程,所有操作都是無序的。
兩條規(guī)定:
a.線程對(duì)共享變量的所有操作必須在工作內(nèi)存中進(jìn)行,不能直接操作主內(nèi)存
b.不同線程間不能訪問彼此的工作內(nèi)存中的變量,線程間變量值的傳遞都必須經(jīng)過主內(nèi)存
2.Synchronized(同步鎖)
Synchronized
實(shí)際上是對(duì)訪問修改共享變量的代碼塊進(jìn)行加互斥鎖,多個(gè)線程對(duì)Synchronized代碼塊的訪問時(shí),某一時(shí)刻僅僅有一個(gè)線程在訪問和修改代碼塊中的內(nèi)容(加鎖),其他所有的線程等待該線程離開代碼塊時(shí)(釋放鎖)才有機(jī)會(huì)進(jìn)入Synchronized代碼塊。
Synchronized
關(guān)鍵字保證了數(shù)據(jù)讀寫一致和可見性等問題,“以時(shí)間換空間”。
修飾一個(gè)類
class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}
修飾一個(gè)方法
public synchronized void method()
{
// todo
}
修飾一個(gè)代碼塊
public void method()
{
synchronized(this){
// todo
}
}
修飾一個(gè)靜態(tài)的方法
public synchronized static void method() {
// todo
}
3.volatile
volatile如何實(shí)現(xiàn)可見性?
volatile變量每次被線程訪問時(shí),都強(qiáng)迫線程從主內(nèi)存中重讀該變量的最新值,而當(dāng)該變量發(fā)生修改變化時(shí),也會(huì)強(qiáng)迫線程將最新的值刷新回主內(nèi)存中。這樣一來,不同的線程都能及時(shí)的看到該變量的最新值。
但是volatile不能保證變量更改的原子性!
比如count++
,這個(gè)操作實(shí)際上是三個(gè)操作的集合,volatile只能保證每一步的操作對(duì)所有線程是可見的,但是假如兩個(gè)線程都需要執(zhí)行count++
,那么這一共6個(gè)操作集合,之間是可能會(huì)交叉執(zhí)行的,那么最后導(dǎo)致xx的結(jié)果可能會(huì)不是所期望的。有可能1號(hào)線程在即將進(jìn)行寫操作時(shí)count值為3;而2號(hào)線程就恰好獲取了寫操作之前的值3,所以1號(hào)線程在完成它的寫操作后count值就為4了,而在2號(hào)線程中count的值還為4,即使2號(hào)線程已經(jīng)完成了寫操作count還是為4。
public class Counter {
private volatile int count;
public int getCount(){
return count;
}
public void increment(){
count++;
}
}
所以對(duì)于count++
這種非原子性操作
,推薦用synchronized:
public class Counter {
private volatile int count;
public int getCount(){
return count;
}
public synchronized void increment(){
count++;
}
}
volatile適用情況
(1)對(duì)變量的寫入操作不依賴當(dāng)前值
比如自增自減、count = count + 5等(不滿足)
(2)當(dāng)前volatile變量不依賴于別的volatile變量
3.synchronized和volatile比較
- synchronized既能保證共享變量可見性,也可以保證鎖內(nèi)操作的原子性;volatile只能保證可見性;
- volatile不需要同步操作,所以效率更高,不會(huì)阻塞線程,但是適用情況比較窄
4.ThreadLocal
ThreadLocal不是為了解決多線程訪問共享變量
,而是為每個(gè)線程創(chuàng)建一個(gè)單獨(dú)的變量副本,提供了保持對(duì)象的方法和避免參數(shù)傳遞的復(fù)雜性。
顧名思義它是local variable(線程局部變量)。它的功用非常簡(jiǎn)單,就是為每一個(gè)使用該變量的線程都提供一個(gè)變量值的副本,是每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)和其它線程的副本沖突。從線程的角度看,就好像每一個(gè)線程都完全擁有該變量。(ThreadLocal采用了“以空間換時(shí)間”的方式)