volatile關(guān)鍵字
定義:對該變量禁止使用CPU緩存,而從主內(nèi)存中讀寫
特性:
- 禁止編碼優(yōu)化(禁止指令重排序)
- 保證變量的線程可見性,即線程B對線程A的操作是可見的,即原則1 遵循h(huán)appens-before原則
- 不會對線程阻塞,而只是對變量的"讀或?qū)?保證原子性,但不對"讀并且寫"保證原子性??梢岳斫鉃橛袃蓚€鎖:讀鎖和寫鎖,但不可同時讀和寫,見increase方法;故此時一寫多讀時可以保證數(shù)據(jù)一致。
若要多寫多讀,synchronize關(guān)鍵字或Lock類
volatile static int inc = 0;
public static void main(String[] args) {
Runnable runnable = ()->{
increase();
};
ExecutorService executorService = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
executorService.execute(runnable);
}
// 等待所有線程結(jié)束
executorService.shutdown();
System.out.println(inc);
}
private static /*synchronized*/ void increase() {
for (int i = 0; i < 1000; i++) {
// 如線程A執(zhí)行至256次內(nèi)存讀到256,線程B已經(jīng)執(zhí)行完1000次寫入內(nèi)存1000,此時線程A第257次內(nèi)存讀到1000,繼續(xù)剩下的743次循環(huán)
inc++; // inc = inc + 1 讀inc并且寫inc
}
}
19418
Process finished with exit code 0
happens-befores原則:
定義:前一個操作的結(jié)果對后續(xù)操作是可見的
共8條原則
主要為
- 順序性
- volatile原則
- 傳遞性
...
見Java并發(fā)編程實戰(zhàn)
java 8大happen-before原則超全面詳解
synchronize關(guān)鍵字
定義:Java中互斥鎖技術(shù)的實現(xiàn)
特性:
- 可修飾方法,代碼塊
class X {
// 修飾非靜態(tài)方法
synchronized void foo() {
// 臨界區(qū)
}
// 修飾靜態(tài)方法
synchronized static void bar() {
// 臨界區(qū)
}
// 修飾代碼塊
Object obj = new Object();
void baz() {
synchronized(obj) {// lock()
// 臨界區(qū)
//unlock()
}
}
}
- 修飾static方法時,實際鎖的是該類的.class對象;修飾非static方法時,鎖的時該this對象
注意:使用鎖synchronized要注意以哪個對象為鎖,和要保護的資源(臨界區(qū)),在同一個鎖下的臨界區(qū)是保證原子性的
class obj {
synchronized(obj.class) static void foo1(){}
// obj.class是單例的
synchronized(this) void foo2(){}
// this是可以new出多個的
}
- wait()、notify()、notifyAll()只能在sychronized代碼塊中使用
sychronized(this){
this.wait() // 此處釋放的鎖一定為this,即鎖對象
// 若鎖對象為targer,則為target.wait()
}
wait():釋放該互斥鎖,同時該線程進入等待隊列,使其他線程可以搶占該鎖 sleep()使線程阻塞,不會釋放鎖
notify(): 隨機通知等待隊列中的一個線程,條件滿足,可以執(zhí)行wait()之后代碼
使用notifyAll():通知隊列中所有線程,條件滿足
Lock接口
定義:Lock為concurrent.locks包下的一個接口,該locks包提供對線程鎖操作的方法。
特性:
1.常用實現(xiàn)類:
ReentrantLock lock = new ReentrantLock(); // 實現(xiàn)Lock接口
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 實現(xiàn)ReadWriteLock,提供讀鎖或?qū)戞i
readWriteLock.writeLock().lock(); // 讀鎖
2.常用方法:
lock.lock();// 獲得鎖
try{
//處理任務(wù)
}catch(Exception ex){
}finally{
lock.unlock(); // 釋放鎖
}
lock.tryLock(); // 嘗試獲得鎖,得到true,得不到false,立即返回
悲觀鎖與樂觀鎖CAS機制
悲觀鎖
定義:總是假設(shè)最壞的情況,假設(shè)每次取數(shù)據(jù)后都認(rèn)為數(shù)據(jù)會被其他線程修改,需要保證數(shù)據(jù)的強一致性。如sychronized關(guān)鍵字和ReentrantLock類,都是悲觀鎖。
樂觀鎖與CAS
定義:假設(shè)每次取得數(shù)據(jù)后,數(shù)據(jù)不會被其他線程修改。
CAS:全稱compareAndSwap,望文生義,即比較與替換。
每次進行對數(shù)據(jù)寫操作時進行一次CAS:
compare:比較工作內(nèi)存中的值A(chǔ)1,是否與主內(nèi)存中地址V中的值A(chǔ)2一致;
swap:若A1與A2一致,則修改地址V中的A2為B;若A1與A2不一致,則重新讀取地址V中的值,再進行任務(wù)處理,稱為回旋,該情況可能一直循環(huán)直到一致為止。
CAS下帶來的ABA問題:
按時間順序有以下任務(wù):
線程1任務(wù):讀地址V,A值修改為B值
線程2任務(wù):讀地址V,A值修改為B值
線程3任務(wù):讀地址V,B值修改為A值
根據(jù)CAS原則,線程1執(zhí)行后,線程2回旋,線程3執(zhí)行,最終值為A
若此時線程2阻塞,則只執(zhí)行1和3,線程2釋放,根據(jù)CAS原則,執(zhí)行完1和3后,線程2回旋判定A值一致,修改為B,最終值為B
解決方法:每次寫數(shù)據(jù)時,加入版本號,判斷A1與A2一致時,同時判斷版本號是否一致
如concurrent包下的Atomic類,為樂觀鎖。