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