要理解java內存模型以及一些處理高並發的技術手段,理解一些主要的硬件知識是必須的。
一個主要CPU運行計算的步驟例如以下:
- 程序以及數據被載入到主內存
- 指令和數據被載入到CPU的快速緩存
- CPU運行指令,把結果寫到快速緩存
- 快速緩存中的數據寫會主內存
- 高並發的問題:
CPU多級緩存:緩存一致性,亂序執行優化
緩存一致性:eg.(i初值為1,兩個線程對i進行加1操作)兩個線程分別讀取i的值存入各自所在的CPU的高速緩存當中,然后線程1進行加1操作,然后把i的最新值1寫入到內存。此時線程2的高速緩存當中i的值還是0,進行加1操作之后,i的值為1,然后線程2把i的值寫入內存。這樣最終的到的結果就是1,而不是2。
解決方法:
- 通過在總線上加LOCK鎖的方式
- 通過緩存一致性協議 核心思想:當CPU向內存讀入數據時,如果發現操作的變量是共享變量,即在其他CPU中也存在該變量的副本,會發出信號通知其他CPU將該變量的緩存行置為無效狀態,因此當其他CPU需要讀取這個變量時,發現自己緩存中緩存該變量的緩存行是無效的,那么就會從內存重新讀取。
java內存模型:JMM規定,抽象結構,同步等八種操作及規則
java並發的優勢和風險
優勢:
- 系統可以處理多個請求,相應快
- 磁盤和CPU利用率提升
缺點:
- 線程過多,會消耗內存
- 線程安全性:
原子性:(Atomic 包、cas、syncronized、lock)
- AtomicXXX:核心是Unsafe.compareAndSwapInt(CAS),將主存的值和預期值進行比較,如果相同才進行更新。
- AtomicLong、LongAdder
- AtomicReference、AtomicReferenceFieldUpdater
- AtomicStampReference
- 鎖:
synchronized同步鎖:修飾4種對象(不能繼承):不可中斷鎖,適合競爭不激烈,可讀性好
JMM關於synchronized的兩條規定:
線程解鎖前,必須把共享變量的最新值刷新到主內存
線程加鎖時,將清空工作內存中共享變量的值,從而使用共享變量時需要從主內存中重新讀取最新的值。
修飾區域:
- 修飾代碼塊
- 修飾方法
- 修飾靜態方法
- 修飾類
lock:可中斷鎖,多樣化同步,競爭激烈時能維持常態
- 可見性(syncronized、volitale)
導致共享變量在線程間不可見的原因是:1.線程交叉執行2.重排序結合線程交叉執行3.共享變量更新后的值沒有在工作內存與主存間及時更新
synchronized
volitale關鍵字(作為狀態標記量)特點:1.可見性2禁止指令重排序(加入內存屏障)
禁止指令重排序:a.當程序執行到volatile變量的讀操作或者寫操作時,在其前面的操作更改肯定全部已經執行,且結果已經對后面的操作可見;在其后面的操作肯定還沒有進行b.在指令優化時,不能把volatile變量后面的語句放到其前面執行。
- 對volatile變量寫操作時,會在讀操作后加入一條Store屏障指令,將本地內存中的共享變量刷新到主內存
- 對volatile變量讀操作時,會在讀操作前加入一條load屏障指令,從主內存中讀取共享變量
使用volitile關鍵字的場景:
1.作為別的線程的標志位
2.雙重校驗鎖DCL
public class Singleton
{
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null)
{
synchronized (Singleton.class)
{
if (singleton == null)
{
singleton = new Singleton();
}
}
}
return singleton;
}
}
- 有序性(happens-before 原則)
- 程序次序規則(一個線程內:按照代碼順序,書寫在前面的操作先行發生於書寫在后面的操作)
- 鎖定規則
- volatile變量規則
- 傳遞規則
- 線程啟動規則
- 線程中斷規則
- 線程終結規則
- 對象終結規則