JMM(Java內(nèi)存模型)
- Java內(nèi)存模型是一種邏輯模型记劝,是一組預(yù)定規(guī)范,定義了每個(gè)變量的訪問(wèn)方式,簡(jiǎn)要運(yùn)行方式如下
- Java內(nèi)存區(qū)域矾湃,描述了Java內(nèi)存的物理分布
JMM分為主內(nèi)存和工作內(nèi)存(椖张睿空間)惊完,JVM會(huì)為每一個(gè)線程分配一個(gè)工作內(nèi)存,Java中所有的實(shí)例對(duì)象都存在主存中处硬,主存對(duì)所有線程共享小槐,但線程不能直接操作主存,只能先進(jìn)行拷貝荷辕,操作完畢后再存進(jìn)主存中凿跳。工作內(nèi)存是私有的,存有主存數(shù)據(jù)的副本疮方,線程間的通信必須通過(guò)主存來(lái)進(jìn)行
JMM的數(shù)據(jù)存儲(chǔ)
方法中的基本數(shù)據(jù)類型存儲(chǔ)在工作內(nèi)存的棧幀中控嗜,對(duì)象實(shí)例的引用也存儲(chǔ)在棧幀中,但值存在主存中骡显,除此之外疆栏,例如成員變量曾掂,靜態(tài)變量等都存在主存中
JMM保證線程安全的三個(gè)特性
原子性:指不可中斷的操作,理論上對(duì)于32位操作系統(tǒng)來(lái)說(shuō)壁顶,long和double數(shù)據(jù)的讀取是非原子操作
可見性:一個(gè)線程間共享的變量可見珠洗,指當(dāng)一個(gè)線程修改了該共享變量的值,其他線程是否能立刻獲取到這一次的修改若专。對(duì)于單線程來(lái)說(shuō)许蓖,這個(gè)問(wèn)題是不存在的,而對(duì)于多線程來(lái)說(shuō)富岳,如果存在工作內(nèi)存和主存的同步延遲蛔糯,就會(huì)出現(xiàn)一個(gè)線程修改變量之后,另一個(gè)線程仍然讀取修改前主存中的變量值
有序性:對(duì)于多線程窖式,編譯成機(jī)器碼后會(huì)出現(xiàn)指令重排的現(xiàn)象蚁飒,對(duì)于一個(gè)線程來(lái)說(shuō),每條指令的執(zhí)行順序是固定的萝喘,但對(duì)于多個(gè)線程來(lái)說(shuō)淮逻,指令的執(zhí)行順序是不固定的
JMM提供的線程安全解決方案
對(duì)數(shù)據(jù)的原子性操作,可以使用java.util.concurrent.atomic.*這個(gè)包阁簸,對(duì)代碼塊的原子性操作爬早,可以使用synchronized關(guān)鍵字或者重入鎖(ReentrantLock)
happens-before原則
一套輔助規(guī)則保證程序執(zhí)行的原子性,可見性和有序性
- 同一線程中启妹,代碼一定按順序執(zhí)行
- 同一個(gè)鎖筛严,先進(jìn)行解鎖才能加鎖(也就是說(shuō)加鎖和解鎖一定是輪流執(zhí)行)
- 對(duì)于volatile變量,強(qiáng)制從主存中讀取饶米,并且在改變后強(qiáng)制刷新到主存
- 線程啟動(dòng)規(guī)則:線程的start()方法先于它的每一個(gè)動(dòng)作
- 線程終止規(guī)則:線程的終止在所有動(dòng)作之后(例如線程A中執(zhí)行了線程B.join()桨啃,那么B中所有的對(duì)共享變量的操作結(jié)果,都能在B.join()結(jié)束后被A讀取到)
- 線程中斷規(guī)則:interrupt()方法先執(zhí)行檬输,Thread.interrupted()再返回true
- 對(duì)象終結(jié)規(guī)則:對(duì)象的構(gòu)造函數(shù)先于finalize()方法(但一般不會(huì)Override finalize()方法)
新建線程的三種方式
- 繼承Thread類
- 實(shí)現(xiàn)Runnable接口(lambda照瘾,匿名內(nèi)部類等)
- 實(shí)現(xiàn)Callable接口
線程狀態(tài)
名稱 | 狀態(tài) |
---|---|
NEW | 構(gòu)建完畢,但是沒(méi)有start() |
RUNNABLE | 運(yùn)行中丧慈,包括就緒和運(yùn)行狀態(tài) |
BLOCKED | 線程阻塞析命,被鎖住 |
WAITING | 線程進(jìn)入等待狀態(tài),需要其他線程通知或中斷 |
TIME_WAITING | 超時(shí)等待逃默,可以在指定時(shí)間自行改變 |
TERMINATED | 線程終止鹃愤,已執(zhí)行完畢 |
線程操作
- interrupt 不改變線程的狀態(tài)情況下實(shí)現(xiàn)線程間的交互,可以用于線程結(jié)束時(shí)清理資源
- join 將另一個(gè)線程加入當(dāng)前線程笑旺,同步執(zhí)行
- sleep 讓線程按指定時(shí)間休眠昼浦,注意和Object.wait()的比較
- yield 讓當(dāng)前線程讓出cpu,注意與sleep比較
interrupt機(jī)制
當(dāng)調(diào)用thread.interrupt()時(shí)
- 線程在sleep, wait, join, 方法會(huì)被喚醒筒主,并且需要對(duì)interruptedException進(jìn)行處理
- 線程運(yùn)行(RUNNABLE)中关噪,會(huì)設(shè)置interrupt標(biāo)志位鸟蟹,可通過(guò)isInterrupted()進(jìn)行查看并進(jìn)行處理
守護(hù)線程
例如GC,JIT線程就屬于守護(hù)線程使兔,可以通過(guò)setDaemon設(shè)置建钥,當(dāng)虛擬機(jī)只有守護(hù)線程時(shí),線程會(huì)執(zhí)行退出
Tips
- yield VS sleep
- yield只是使當(dāng)前線程讓出cpu虐沥,不一定會(huì)切換到其他線程
- yield讓出cpu后熊经,只有與當(dāng)前線程具有相同優(yōu)先級(jí)的線程有競(jìng)爭(zhēng)權(quán),而sleep交出的時(shí)間片欲险,其他線程都可以競(jìng)爭(zhēng)
- 編譯器重排和處理器指令重排:
- 簡(jiǎn)單地說(shuō)镐依,比如線程A有指令A(yù)1,A2,A3,線程B有指令B1,B2,B3天试,A1A2A3B1B2B3槐壳,A1B1B2B3A2A3等等都是可能出現(xiàn)的指令順序
- 賦值語(yǔ)句的重排屬于編譯器重排,而機(jī)器碼指令的重排數(shù)據(jù)處理器重排
ref
http://www.reibang.com/p/f65ea68a4a7f
https://blog.csdn.net/javazejian/article/details/72772461
https://blog.csdn.net/javazejian/article/details/72828483