一.內(nèi)存模型:
- 每一個(gè)線程有一個(gè)工作內(nèi)存,和主存是獨(dú)立的衣摩。
- 工作內(nèi)存存放主存重變量的值得拷貝
線程獨(dú)享的工作內(nèi)存和主存的關(guān)系昂验,如下圖:
- 當(dāng)數(shù)據(jù)從主內(nèi)存復(fù)制到工作存儲(chǔ)時(shí),必須出現(xiàn)兩個(gè)動(dòng)作:第一艾扮,由主內(nèi)存執(zhí)行的讀(read)操作既琴;第二,由工作內(nèi)存執(zhí)行的相應(yīng)的load操作泡嘴;
- 當(dāng)數(shù)據(jù)從工作內(nèi)存拷貝到主內(nèi)存時(shí)甫恩,也出現(xiàn)兩個(gè)操作:第一個(gè),由工作內(nèi)存執(zhí)行的存儲(chǔ)(store)操作磕诊;第二填物,由主內(nèi)存執(zhí)行的相應(yīng)的寫(xiě)(write)操作。
- 每一個(gè)操作都是原子的霎终,即執(zhí)行期間不會(huì)被中斷滞磺,即read不會(huì)中斷,但是read和load直接會(huì)有中斷莱褒。
- 對(duì)于普通變量击困,一個(gè)線程中更新的值,不能馬上反應(yīng)在其他變量中广凸。如果需要在其他線程中立即可見(jiàn)阅茶,需要使用 volatile 關(guān)鍵字。
jmm控制共享變量和共享變量副本直接的拷貝谅海,基于cpu優(yōu)化的原因脸哀,會(huì)有一定程度的延遲。
二.原子性:
原子性是指一個(gè)操作是不可中斷的扭吁。即使是在多個(gè)線程一起執(zhí)行的時(shí)候撞蜂,一個(gè)操作一旦開(kāi)始盲镶,就不會(huì)被其他線程干擾。
i++是原子操作嗎蝌诡?
不是溉贿。i++由三個(gè)原子操作組成:
- 線程私有內(nèi)存從主存把i的值拷貝下來(lái)。
- 執(zhí)行i++操作浦旱。
- 把i的值賦給主存宇色。
三.可見(jiàn)性
可見(jiàn)性是指當(dāng)一個(gè)線程修改了某一個(gè)共享變量的值,其他線程是否能夠立即知道這個(gè)修改颁湖。
– 編譯器優(yōu)化
– 硬件優(yōu)化(如寫(xiě)吸收宣蠕,批操作)
這幅圖展示了發(fā)生可見(jiàn)性問(wèn)題的一種可能。如果在CPU1和CPU2
volatile:
volatile修飾的變量爷狈,在主內(nèi)存和線程私有內(nèi)存直接拷貝不會(huì)有延遲植影。
public class VolatileStopThread extends Thread {
private volatile boolean stop=false;
public void stepMe(){
stop=true;
}
public void run(){
int i=0;
while (!stop){
i++;
}
System.out.println("Stop thread");
}
public static void main(String[] args) throws InterruptedException{
VolatileStopThread t=new VolatileStopThread();
t.start();//子線程
Thread.sleep(1000);
t.stepMe();//主線程
Thread.sleep(1000);
}
}
volatile 不能代替鎖
- 一般認(rèn)為volatile比鎖性能好(不絕對(duì))
- 選擇使用volatile的條件是:語(yǔ)義是否滿足應(yīng)用。
- 沒(méi)有volatile -server運(yùn)行 無(wú)法停止涎永,因?yàn)閂olatileStopThread只在線程的本地存儲(chǔ)區(qū)查看stop的值思币。
- 可見(jiàn)性:一個(gè)線程修改了變量,其他線程可以立即知道羡微。
- 保證可見(jiàn)性的方法:
- volatile
- synchronized(unlock之前谷饿,寫(xiě)變量值回主存)
- final(一旦初始化完成,其他線程就可見(jiàn))妈倔。
四.有序性:
在并發(fā)時(shí)博投,程序的執(zhí)行可能會(huì)出現(xiàn)亂序。
1.在本線程內(nèi)盯蝴,操作都是有序的毅哗。(不會(huì)破壞語(yǔ)義,所以看似有序)
2.在線程外觀察捧挺,操作都是無(wú)序的虑绵。(造成原因:1.指令重排。2.主內(nèi)存同步延遲--可見(jiàn)性)
class OrderExample {
int a = 0;
boolean flag = false;
public void writer() {
a = 1;
flag = true;
}
public void reader() {
if (flag) {
int i = a +1;
……
}
}
}
線程A首先執(zhí)行writer()方法
線程B線程接著執(zhí)行reader()方法
線程B在int i=a+1 是不一定能看到a已經(jīng)被賦值為1
因?yàn)樵趙riter中闽烙,兩句話順序可能打亂:
線程A
flag=true
a=1
線程B
flag=true;在a = 1;之前執(zhí)行翅睛。(此時(shí)a=0) 就是說(shuō)flag=true;和a = 1;這兩行很難預(yù)料誰(shuí)先執(zhí)行。
如何保證有序呢:加synchronized同步,同步后黑竞,即使做了writer重排捕发,因?yàn)榛コ獾木壒剩瑀eader 線程看writer線程也是順序執(zhí)行的很魂。
class OrderExample {
int a = 0;
boolean flag = false;
public synchronized void writer() {
a = 1;
flag = true;
}
public synchronized void reader() {
if (flag) {
int i = a +1;
……
}
}
}
- 指令重排:編譯器為了使性能優(yōu)化扎酷,編譯器會(huì)將代碼指令的順序進(jìn)行調(diào)整。保證線程內(nèi)串行語(yǔ)義遏匆,不保證多線程直接的語(yǔ)義霞玄。如:
寫(xiě)后讀 a=1;b=a; 寫(xiě)一個(gè)變量后骤铃,再讀這個(gè)位置。
寫(xiě)后寫(xiě) a=1;a=2; 寫(xiě)一個(gè)變量后坷剧,再寫(xiě)這個(gè)變量
讀后寫(xiě) a=b;b=1; 讀一個(gè)變量后,再讀這個(gè)變量喊暖。
以上語(yǔ)句不能重排惫企。
編譯器不考慮多線程間的語(yǔ)義。
可重拍:a=1;b=2;
指令重排的基本原則:
- 程序順序原則:一個(gè)線程內(nèi)保證語(yǔ)義的串行性
- volatile規(guī)則:volatile變量的寫(xiě)陵叽,先發(fā)生于讀
- 鎖規(guī)則:解鎖(unlock)必然發(fā)生在隨后的加鎖(lock)前
- 傳遞性:A指令先于B指令狞尔,B指令先于C指令 那么A指令必然先于C指令
- 線程的start方法先于它的每一個(gè)動(dòng)作
- 線程的所有操作先于線程的終結(jié)(Thread.join())
- 線程的中斷(interrupt())先于被中斷線程的代碼
- 對(duì)象的構(gòu)造函數(shù)執(zhí)行結(jié)束先于finalize()方法