java----volatile, 用更低的代價替代同步
public class MyTestThread extends MyTest implements Runnable {
private boolean _done = false;
public synchronized boolean getDone() {
return _done;
}
public synchronized void setDone(boolean b) {
_done = b;
}
public void run() {
boolean done;
done = getDone();
while (!done) {
repaint();
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
return;
}
}
}
}
// 或者:
public class MyTestThread extends MyTest implements Runnable {
private boolean _done = false;
public void setDone(boolean b) {
synchronized (this) {
_done = b;
}
}
public void run() {
boolean done;
synchronized (this) {
done = _done;
}
while (!done) {
repaint();
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
return;
}
}
}
}
// 但是,通過volatile關鍵字,我們可以大大簡化:
public class MyTestThread extends MyTest implements Runnable {
private volatile boolean done = false;
public void run() {
while (!done) {
repaint();
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
return;
}
}
}
public void setDone(boolean b) {
done = b;
}
}
//以上每步操作都是原子性的,沒什麼問題,但是如果是 volatile i ; ...... i++這類非原子性操作會有問題。
volatile, 用更低的代價替代同步
為什麼使用volatile比同步代價更低?
同步的代價, 主要由其覆蓋範圍決定, 如果可以降低同步的覆蓋範圍, 則可以大幅提升程式效能.
而volatile的覆蓋範圍僅僅變數級別的. 因此它的同步代價很低.
volatile原理是什麼?
volatile的語義, 其實是告訴處理器, 不要將我放入工作記憶體, 請直接在主存操作我.(工作記憶體詳見java記憶體模型)
因此, 當多核或多執行緒在訪問該變數時, 都將直接操作主存, 這從本質上, 做到了變數共享.
volatile的有什麼優勢?
1, 更大的程式吞吐量
2, 更少的程式碼實現多執行緒
3, 程式的伸縮性較好
4, 比較好理解, 無需太高的學習成本
volatile有什麼劣勢?
1, 容易出問題
2, 比較難設計
volatile運算存在髒資料問題
volatile僅僅能保證變數可見性, 無法保證原子性.
volatile的race condition示例:
private volatile int i = 0;
public void increase() {
i++;
}
public int getValue() {
return i;
}
}
當多執行緒執行increase方法時, 是否能保證它的值會是線性遞增的呢?
答案是否定的.
原因:
這裡的increase方法, 執行的操作是i++, 即 i = i + 1;
針對i = i + 1, 在多執行緒中的運算, 本身需要改變i的值.
如果, 在i已從記憶體中取到最新值, 但未與1進行運算, 此時其他執行緒已數次將運算結果賦值給i.
則當前執行緒結束時, 之前的數次運算結果都將被覆蓋.
即, 執行100次increase, 可能結果是 < 100.
一般來說, 這種情況需要較高的壓力與併發情況下, 才會出現.
如何避免這種情況?
解決以上問題的方法:
一種是 操作時, 加上同步.這種方法, 無疑將大大降低程式效能, 且違背了volatile的初衷.
第二種方式是, 使用硬體原語(CAS), 實現非阻塞演算法,從CPU原語上, 支援變數級別的低開銷同步.CPU原語-比較並交換(CompareAndSet),實現非阻塞演算法
什麼是CAS?
cas是現代CPU提供給併發程式使用的原語操作. 不同的CPU有不同的使用規範.
在 Intel 處理器中,比較並交換通過指令的 cmpxchg 系列實現。
PowerPC 處理器有一對名為“載入並保留”和“條件儲存”的指令,它們實現相同的目地;
MIPS 與 PowerPC 處理器相似,除了第一個指令稱為“載入連結”。
CAS 操作包含三個運算元 —— 記憶體位置(V)、預期原值(A)和新值(B)
什麼是非阻塞演算法?
一個執行緒的失敗或掛起不應該影響其他執行緒的失敗或掛起.這類演算法稱之為非阻塞(nonblocking)演算法
對比阻塞演算法:
如果有一類併發操作, 其中一個執行緒優先得到物件監視器的鎖, 當其他執行緒到達同步邊界時, 就會被阻塞.
直到前一個執行緒釋放掉鎖後, 才可以繼續競爭物件鎖.(當然,這裡的競爭也可是公平的, 按先來後到的次序)
CAS 原理:
我認為位置 V 應該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。
CAS使用示例(jdk 1.5 併發包 AtomicInteger類分析:)
* Atomically sets to the given value and returns the old value.
*
* @param newValue
* the new value
* @return the previous value
*/
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
這個方法是, AtomicInteger類的常用方法, 作用是, 將變數設定為指定值, 並返回設定前的值.
它利用了cpu原語compareAndSet來保障值的唯一性.
另, AtomicInteger類中, 其他的實用方法, 也是基於同樣的實現方式.
比如 getAndIncrement, getAndDecrement, getAndAdd等等.
CAS語義上存在的"ABA 問題"
什麼是ABA問題?
假設, 第一次讀取V地址的A值, 然後通過CAS來判斷V地址的值是否仍舊為A, 如果是, 就將B的值寫入V地址,覆蓋A值.
但是, 語義上, 有一個漏洞, 當第一次讀取V的A值, 此時, 記憶體V的值變為B值, 然後在未執行CAS前, 又變回了A值.
此時, CAS再執行時, 會判斷其正確的, 並進行賦值.
這種判斷值的方式來斷定記憶體是否被修改過, 針對某些問題, 是不適用的.
為了解決這種問題, jdk 1.5併發包提供了AtomicStampedReference(有標記的原子引用)類, 通過控制變數值的版本來保證CAS正確性.
其實, 大部分通過值的變化來CAS, 已經夠用了.
jdk1.5原子包介紹(基於volatile)
包的特色:
1, 普通原子數值型別AtomicInteger, AtomicLong提供一些原子操作的加減運算.
2, 使用瞭解決髒資料問題的經典模式-"比對後設定", 即 檢視主存中資料是否與預期提供的值一致,如果一致,才更新.
3, 使用AtomicReference可以實現對所有物件的原子引用及賦值.包括Double與Float,但不包括對其的計算.浮點的計算,只能依靠同步關鍵字或Lock介面來實現了.
4, 對陣列元素裡的物件,符合以上特點的, 也可採用原子操作.包裡提供了一些陣列原子操作類AtomicIntegerArray, AtomicLongArray等等.
5, 大幅度提升系統吞吐量及效能.
相關文章
- 為什麼用元空間替代永久代?
- PSRAM是非同步SRAM的理想替代品非同步
- Nvidia釋出更快、功耗更低的新一代圖形加速卡
- ASP.NET Core 8 的記憶體佔用可以更低嗎?ASP.NET記憶體
- HTC Vive Pro宣佈發售:6488元 一代同步降價
- DEVOPS也是要有代價的dev
- 用 useContext + useReducer 替代 reduxContextuseReducerRedux
- 【NeurIPS 2022】影片動作識別,AFNet 用更低的成本接收更多資料
- 用eBPF/XDP來替代LVSeBPF
- HTTPie:替代 Curl 和 Wget 的現代 HTTP 命令列客戶端HTTPwget命令列客戶端
- 最小連通代價
- 最小編輯代價
- MVP風險太大,用EVA替代MVP
- 更安全、更低耗的微服務架構改造之道微服務架構
- 函式呼叫的代價與優化函式優化
- 同步阻塞、同步非阻塞、多路複用的介紹
- Rocketgraph:用Golang編寫的Firebase的開源替代品Golang
- 終究為自己的 curd 付出了代價
- 寵物AB面:愛與陪伴的代價
- YouGov:65%的印度人計劃替代TikTok應用Go
- 用於持續整合的13種Jenkins替代方案 -DEVJenkinsdev
- 面向雲時代的龍蜥作業系統 是 CentOS 替代的最佳選擇作業系統CentOS
- Cypress同步高速SRAM的應用
- CSS文字超出長度用省略號替代CSS
- 模型表示及代價函式模型函式
- Nvidia 悄悄推出更快、更低功耗的 Tesla GPU 加速器GPU
- 完美匹配企業需求的FTP替代軟體,需要具備哪些功能和價值?FTP
- 機器學習時代,運營人如何提高不可替代性?機器學習
- 除了傳統的GPU(圖形處理單元),目前有幾種不同的計算架構和硬體平臺可以作為替代方案,用於加速圖形渲染、科學計算、機器學習和其他高平行計算任務。這些替代方案在某些應用場景下能夠提供更高效的計算效能或更低的功耗。以下是一些主要的替代方案:GPU架構機器學習
- 2.3.6.1 單個應用程式的同步
- 用Rust替代Java重寫DNS解析器RustJavaDNS
- 從同步原語看非阻塞同步以及Java中的應用Java
- [譯] JSX 的替代方案JS
- singleLine=“true“的替代方法
- Java微服務:用Spark替代SpringBoot才是正確的方式 - Christian LusardiJava微服務SparkSpring Boot
- V8替代?用Rust編寫的JavaScript引擎Boa釋出RustJavaScript
- 非同步社群本週半價電子書非同步
- 成為真正的變革型CIO,代價幾何?