多任務處理,在計算機硬件上的問題
1. 緩存一致性
計算機中,CPU的運行速度和內存的讀取速度,有幾個數(shù)量級的差異; 為了解決這個問題, 現(xiàn)代計算機在CPU和內存之間,都加入了和CPU運算速度匹配的高速緩存Cache; 運算時, 先將數(shù)據(jù)讀入cache, CPU從緩存中讀取數(shù)據(jù), 運算結束后, CPU將結果寫入緩存, 然后再由緩存把數(shù)據(jù)寫回內存; 基于高速緩存的方案解決了CPU與內存之間的數(shù)速度矛盾, 但是同時引進了一個新問題: 內存一致性; 如果多個CPU單元同時處理同一塊內存區(qū)域的數(shù)據(jù), 可能各個CPU的緩存數(shù)據(jù)不一樣, 那這個時候把數(shù)據(jù)寫回內存, 要怎么處理?
2. 指令重排優(yōu)化
CPU為了盡可能的提升效率,會對輸入代碼進行亂序執(zhí)行優(yōu)化,然后將結果重組,保證結果和順序執(zhí)行是一樣的, 但是并不保證代碼中各個語句執(zhí)行順序和輸入的代碼一致, 因此代碼的順序并不能保證執(zhí)行的順序; Java即時編譯中,也有 指令重排優(yōu)化, 因此, 如果一個計算任務依賴另外一個計算任務的中間結果時, 需要去避免重排造成的問題;
Java內存模型(Java Memory Model, JMM)
1. 主內存和工作內存
Java內存模型規(guī)定了, 代碼運行的所有變量都存儲在主內存中 , 同時, 每條線程還有自己的工作內存, 線程的工作內存中包含了被該線程使用的線程私有變量 和主內存副本 ,線程對變量的所有操作(讀取,賦值等)都是在工作線程進行, 不能直接讀寫主內存中的變量; 主內存包含對象實例數(shù)據(jù),靜態(tài)字段數(shù)據(jù)等, 線程私有變量主要是局部變量和方法參數(shù);
2. 內存間交互操作
主內存和工作內存之間的相互操作, 即變量如何從主內存中讀入到工作內存, 代碼執(zhí)行引擎如何使用, 如何從工作內存寫回主內存; Java內存模型定義了8種操作來實現(xiàn), 并且虛擬機實現(xiàn)時, 這8種操作必須都是原子的,不可再分;
- lock: 用于主內存,把一個變量標記為某條線程獨占狀態(tài)
- unlock: 用于主內存, 把處于lock的變量釋放出來, 釋放出來后變量才可以被其他線程lock
- read: 用于主內存, 把變量從主內存?zhèn)鬏數(shù)焦ぷ鲀却? 方便后續(xù)的load使用
- load: 用于工作內存, 把從主內存中read的變量放入工作內存的變量副本中
- use: 用于工作內存, 把工作內存的變量傳遞給執(zhí)行引擎去使用; 虛擬機需要使用變量時,執(zhí)行此操作
- assign: 用于工作內存, 把從執(zhí)行引擎收到的值, 賦值給工作內存中的變量; 虛擬機遇到變量賦值語句時,執(zhí)行此操作
- store: 用于工作內存, 把工作內存中的變量傳回到主內存, 以便后續(xù)的write操作
- write: 用于主內存, 把store操作的變量寫入到主內存
內存模型與線程安全
并發(fā)過程中實現(xiàn)線程安全, 通常是要保證代碼塊是原子的,有序執(zhí)行的, 同時共享變量的修改對線程都是可見的; 而Java內存模型也是圍繞著并發(fā)過程中, 如何處理原子性, 可見性, 有序性這3個特征來建立的;
原子性(Atomicity)
Java內存模型中, read,load,use,assign,store,write都是原子性操作, 因此, 對于基本數(shù)據(jù)類型的讀寫訪問,都可以認為是具有原子性的, (內存模型允許對long,double這類64位的數(shù)據(jù)讀寫操作, 劃分成兩次32位的操作來進行,因此對64位數(shù)據(jù)的操作可能不具有原子性, 但是目前的虛擬機實現(xiàn),都是把這類64位數(shù)據(jù)作為原子性操作);
如果需要大范圍的保證原子性, 內存模型中的lock和unlock可以實現(xiàn); 雖說虛擬機對內存的lock和unlock并沒開放給用戶, 但提供了更高層次的指令, 反應到Java代碼中就是同步塊--synchronize關鍵字, 和java.util.concurrent.locks.Lock的操作, 因此在synchronize代碼塊及Lock.lock和unlock之間的代碼塊也具有原子性
可見性(Visibility)
可見性是指當一個線程修改了共享變量的值, 其他線程能夠立即得知這個修改
volatile修飾的變量,每次修改后,都會立即同步到主內存,每次使用時,都會去從主內存刷新,因此, volatile修飾的變量在多線程時保證了可見性;
除volatile之外, 同步塊和final關鍵字也能實現(xiàn)可見性; 同步塊的可見性, 是由Java內存模型中,對變量執(zhí)行unlock操作,必須將變量同步回主內存這一規(guī)則保證的; final的可見性是指: final修飾的字段,一旦初始化完成,在其他線程就都能看見final字段的值,并且不可修改;
有序性(Ordering)
Java的volatile關鍵字和代碼的同步塊可以保證有序性;
volatile關鍵字本身包含了禁止指令重排的語意;
同步塊的有序性, 是由Java內存模型中,一個變量在同一時刻,只允許一條線程對其進行l(wèi)ock操作的規(guī)則保證的, 這一規(guī)則決定了持有同一個鎖的多個同步塊只能串行進入;