日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

线程安全的实现方法

發(fā)布時間:2024/10/14 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 线程安全的实现方法 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

1. 互斥同步

synchronized 和 ReentrantLock。

2. 非阻塞同步

互斥同步最主要的問題就是線程阻塞和喚醒所帶來的性能問題,因此這種同步也稱為阻塞同步。

互斥同步屬于一種悲觀的并發(fā)策略,總是認(rèn)為只要不去做正確的同步措施,那就肯定會出現(xiàn)問題。

無論共享數(shù)據(jù)是否真的會出現(xiàn)競爭,它都要進(jìn)行加鎖(這里討論的是概念模型,實際上虛擬機(jī)會優(yōu)化掉很大一部分不必要的加鎖) 、用戶態(tài)核心態(tài)轉(zhuǎn)換、維護(hù)鎖計數(shù)器和檢查是否有被阻塞的線程需要喚醒等操作。

2.1 CAS

隨著硬件指令集的發(fā)展,我們可以使用基于沖突檢測的樂觀并發(fā)策略:先進(jìn)行操作,如果沒有其它線程爭用共享數(shù)據(jù),那操作就成功了,否則采取補(bǔ)償措施(不斷地重試,直到成功為止) 。

這種樂觀的并發(fā)策略的許多實現(xiàn)都不需要將線程阻塞,因此這種同步操作稱為非阻塞同步。

樂觀鎖需要操作和沖突檢測這兩個步驟具備原子性,這里就不能再使用互斥同步來保證了,只能靠硬件來完成。硬件支持的原子性操作最典型的是:比較并交換(Compare-and-Swap,CAS)。

CAS 指令需要有 3 個操作數(shù),分別是內(nèi)存地址V、舊的預(yù)期值 A 和新值 B。當(dāng)執(zhí)行操作時,只有當(dāng) V 的值等于 A,才將 V 的值更新為 B。

2.2?AtomicInteger

J.U.C 包里面的整數(shù)原子類 AtomicInteger,其中的 compareAndSet() 和getAndIncrement() 等方法都使用了 Unsafe 類的 CAS 操作。

以下代碼使用了 AtomicInteger 執(zhí)行了自增的操作。

private AtomicInteger cnt = new AtomicInteger();public void add() {cnt.incrementAndGet(); }

以下代碼是 incrementAndGet() 的源碼,它調(diào)用了 unsafe 的 getAndAddInt() 。

public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }

以下代碼是 getAndAddInt() 源碼,var1 指示對象內(nèi)存地址,var2 指示該字段相對對象內(nèi)存地址的偏移,var4 指示操作需要加的數(shù)值,這里為 1。

public final int getAndAddInt(Object var1, long var2, int var4){int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5; }

通過getIntVolatile(var1, var2) 得到舊的預(yù)期值,通過調(diào)用 compareAndSwapInt() 來進(jìn)行 CAS 比較,如果該字段內(nèi)存地址中的值等于 var5,那么就更新內(nèi)存地址為var1+var2 的變量為 var5+var4。

可以看到 getAndAddInt() 在一個循環(huán)中進(jìn)行,發(fā)生沖突的做法是不斷的進(jìn)行重試。

3. 無同步方案

要保證線程安全,并不是一定就要進(jìn)行同步。

如果一個方法本來就不涉及共享數(shù)據(jù),那它自然就無須任何同步措施去保證正確性。

3.1 棧封閉

多個線程訪問同一個方法的局部變量時,不會出現(xiàn)線程安全問題,因為局部變量存儲在虛擬機(jī)棧中,屬于線程私有的。

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;public class StackClosedExample {public void add100() {int cnt = 0;for (int i = 0; i < 100; i++) {cnt++;}System.out.println(cnt);} }public static void main(String[] args) {StackClosedExample example = new StackClosedExample();ExecutorService executorService = Executors.newCachedThreadPool();executorService.execute(() -> example.add100());executorService.execute(() -> example.add100());executorService.shutdown(); }

運(yùn)行結(jié)果:

3.2?線程本地存儲(Thread Local Storage)

如果一段代碼中所需要的數(shù)據(jù)必須與其他代碼共享,那就看看這些共享數(shù)據(jù)的代碼是否能保證在同一個線程中執(zhí)行。

如果能保證,我們就可以把共享數(shù)據(jù)的可見范圍限制在同一個線程之內(nèi),這樣,無須同步也能保證線程之間不出現(xiàn)數(shù)據(jù)爭用的問題。

符合這種特點(diǎn)的應(yīng)用并不少見:

  • 大部分使用消費(fèi)隊列的架構(gòu)模式(如“生產(chǎn)者-消費(fèi)者”模式) 都會將產(chǎn)品的消費(fèi)過程盡量在一個線程中消費(fèi)完。
  • 其中最重要的一個應(yīng)用實例就是經(jīng)典 Web 交互模型中的“一個請求對應(yīng)一個服務(wù)器線程”(Thread-perRequest) 的處理方式,這種理方式的廣泛應(yīng)用使得很多 Web 服務(wù)端應(yīng)用都可以使用線程本地存儲來解決線程安全問題。

可以使用 java.lang.ThreadLocal 類來實現(xiàn)線程本地存儲功能。

對于以下代碼,thread1 中設(shè)置 threadLocal 為 1,而 thread2 設(shè)置 threadLocal 為2。過了一段時間之后,thread1 讀取 threadLocal 依然是 1,不受 thread2 的影響。

public class ThreadLocalExample {public static void main(String[] args) {ThreadLocal threadLocal = new ThreadLocal();Thread thread1 = new Thread(() -> {threadLocal.set(1);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(threadLocal.get());threadLocal.remove();});Thread thread2 = new Thread(() -> {threadLocal.set(2);threadLocal.remove();});thread1.start();thread2.start();} }

運(yùn)行結(jié)果:

為了理解 ThreadLocal,先看以下代碼:

public class ThreadLocalExample1 {public static void main(String[] args) {ThreadLocal threadLocal1 = new ThreadLocal();ThreadLocal threadLocal2 = new ThreadLocal();Thread thread1 = new Thread(() -> {threadLocal1.set(1);threadLocal2.set(1);});Thread thread2 = new Thread(() -> {threadLocal1.set(2);threadLocal2.set(2);});thread1.start();thread2.start();} }

它所對應(yīng)的底層結(jié)構(gòu)圖為:

每個 Thread 都有一個 ThreadLocal.ThreadLocalMap 對象,Thread 類中就定義了ThreadLocal.ThreadLocalMap 成員。

/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;

當(dāng)調(diào)用一個 ThreadLocal 的 set(T value) 方法時,先得到當(dāng)前線程的ThreadLocalMap 對象,然后將 ThreadLocal->value 鍵值對插入到該 Map 中。

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value); }

get() 方法類似。

public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue(); }

ThreadLocal 從理論上講并不是用來解決多線程并發(fā)問題的,因為根本不存在多線程競爭。

在一些場景 (尤其是使用線程池) 下,由于 ThreadLocal.ThreadLocalMap 的底層數(shù)據(jù)結(jié)構(gòu)導(dǎo)致 ThreadLocal 有內(nèi)存泄漏的情況,應(yīng)該盡可能在每次使用 ThreadLocal后手動調(diào)用 remove(),以避免出現(xiàn) ThreadLocal 經(jīng)典的內(nèi)存泄漏甚至是造成自身業(yè)務(wù)混亂的風(fēng)險。

3.3?可重入代碼(Reentrant Code)

這種代碼也叫做純代碼(Pure Code) ,可以在代碼執(zhí)行的任何時刻中斷它,轉(zhuǎn)而去執(zhí)行另外一段代碼(包括遞歸調(diào)用它本身) ,而在控制權(quán)返回后,原來的程序不會出現(xiàn)任何錯誤。

可重入代碼有一些共同的特征,例如不依賴存儲在堆上的數(shù)據(jù)和公用的系統(tǒng)資源、用到的狀態(tài)量都由參數(shù)中傳入、不調(diào)用非可重入的方法等。

?

總結(jié)

以上是生活随笔為你收集整理的线程安全的实现方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。