最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

重新學(xué)習(xí)Java線程原語

2023-04-21 15:00 作者:信碼由韁  | 我要投稿

Synchronized曾經(jīng)是一個(gè)革命性的技術(shù),在當(dāng)前仍然有重要的用途。但是,現(xiàn)在是時(shí)候轉(zhuǎn)向更新的Java線程原語,同時(shí)重新考慮我們的核心邏輯。

自從Java第一個(gè)測(cè)試版以來,我就一直在使用它。從那時(shí)起,線程就是我最喜歡的特性之一。Java是第一種在編程語言本身中引入線程支持的語言。那是一個(gè)具有爭(zhēng)議的決定。在過去的十年中,每種編程語言都競(jìng)相引入async/await,甚至Java也有一些第三方支持......但是Java選擇了引入更優(yōu)越的虛擬線程(Loom項(xiàng)目)。本文并不討論這個(gè)問題。

我覺得這很好,證明了Java的核心實(shí)力。Java不僅僅是一種語言,還是一種文化。這種文化注重深思熟慮的變革,而不是盲目跟隨時(shí)尚潮流。

在本文中,我想重新探討Java中的線程編程舊方法。我習(xí)慣使用synchronized、wait、notify等技術(shù)。但是,?“然而,這些方法已經(jīng)不再是Java中線程處理的最佳方式。?我也是問題的一部分。我還是習(xí)慣于使用這些技術(shù),發(fā)現(xiàn)很難適應(yīng)自Java 5以來就存在的一些API。這是一種習(xí)慣的力量。?雖然可以討論許多處理線程的出色API,但我想在這里專注討論鎖,因?yàn)樗鼈兪腔A(chǔ)但極為重要的。

Synchronized 與 ReentrantLock

我猶豫放棄使用 synchronized 的原因是,并沒有更好的替代方案。現(xiàn)在棄用 synchronized 的主要原因是,它可能會(huì)在 Loom 中觸發(fā)線程固定,這并不理想。JDK 21 可能會(huì)修復(fù)這個(gè)問題(當(dāng) Loom 正式發(fā)布時(shí)),但還有一些理由棄用它。

synchronized 的直接替代品是 ReentrantLock。不幸的是,ReentrantLock 相比 synchronized 很少有優(yōu)勢(shì),因此遷移的好處最多是存疑的。事實(shí)上,它有一個(gè)主要的缺點(diǎn)。為了了解這一點(diǎn),讓我們看一個(gè)例子。下面是我們?nèi)绾问褂?synchronized:

synchronized(LOCK) {

????// safe code

}

LOCK.lock();

try {

????// safe code

} finally {

????LOCK.unlock();

}

ReentrantLock?的第一個(gè)缺點(diǎn)是冗長(zhǎng)。我們需要try塊,因?yàn)槿绻趬K內(nèi)部發(fā)生異常,鎖將保持。而 synchronized 則會(huì)自動(dòng)處理異常。

有些人會(huì)使用?AutoClosable?對(duì)鎖進(jìn)行封裝,大概是這樣的:

public class ClosableLock implements AutoCloseable {

???private final ReentrantLock lock;

???public ClosableLock() {

???????this.lock = new ReentrantLock();

???}

???public ClosableLock(boolean fair) {

???????this.lock = new ReentrantLock(fair);

???}

???@Override

???public void close() throws Exception {

???????lock.unlock();

???}

???public ClosableLock lock() {

???????lock.lock();

???????return this;

???}

???public ClosableLock lockInterruptibly() throws InterruptedException {

???????lock.lock();

???????return this;

???}

???public void unlock() {

???????lock.unlock();

???}

}

注意,我沒有實(shí)現(xiàn) Lock 接口,這本來是最理想的。這是因?yàn)?lock 方法返回了可自動(dòng)關(guān)閉的實(shí)現(xiàn),而不是?void。

一旦我們這樣做了,我們就可以編寫更簡(jiǎn)潔的代碼,比如這樣:

try(LOCK.lock()) {

????/


我喜歡代碼更簡(jiǎn)潔的寫法,但是這個(gè)方法存在一些問題,因?yàn)?try-with-resource 語句是用于清理資源的,而我們正在重復(fù)使用鎖對(duì)象。雖然調(diào)用了 close 方法,但是我們會(huì)再次在同一個(gè)對(duì)象上調(diào)用它。我認(rèn)為,將 try-with-resource 語法擴(kuò)展到支持鎖接口可能是個(gè)好主意。但在此之前,這個(gè)技巧可能不值得采用。

ReentrantLock 的優(yōu)勢(shì)

使用ReentrantLock的最大原因是Loom支持。其他的優(yōu)點(diǎn)也不錯(cuò),但沒有一個(gè)是“殺手級(jí)功能”。

我們可以在方法之間使用它,而不是在一個(gè)連續(xù)的代碼塊中使用。但是這可能不是一個(gè)好主意,因?yàn)槟阆MM量減少鎖定區(qū)域,并且失敗可能會(huì)成為一個(gè)問題。我不認(rèn)為這個(gè)特性是一個(gè)優(yōu)點(diǎn)。

ReentrantLock提供了公平鎖(fairness)的選項(xiàng)。這意味著它會(huì)先服務(wù)于最先停在鎖上的線程。我試圖想到一個(gè)現(xiàn)實(shí)而簡(jiǎn)單的使用案例,但卻無從下手。如果您正在編寫一個(gè)復(fù)雜的調(diào)度程序,并且有許多線程不斷地排隊(duì)等待資源,您可能會(huì)發(fā)現(xiàn)一個(gè)線程由于其他線程不斷到來而被“饑餓”。但是,這種情況可能更適合使用并發(fā)包中的其他選項(xiàng)。也許我漏掉了什么……

lockInterruptibly()?方法允許我們?cè)诰€程等待鎖時(shí)中斷它。這是一個(gè)有趣的特性,但是很難找到一個(gè)真正實(shí)際應(yīng)用場(chǎng)景。如果你編寫的代碼需要非??焖夙憫?yīng)中斷,你需要使用?lockInterruptibly()?API 來獲得這種能力。但是,你通常在?lock()方法內(nèi)部花費(fèi)多長(zhǎng)時(shí)間呢?

這種情況可能只在極端情況下才會(huì)有影響,大多數(shù)人在編寫高級(jí)多線程代碼時(shí)可能不會(huì)遇到這種情況。

ReadWriteReentrantLock

更好的方法是使用ReadWriteReentrantLock。大多數(shù)資源都遵循頻繁讀取、少量寫入的原則。由于讀取變量是線程安全的,除非正在寫入變量,否則沒有必要加鎖。這意味著我們可以將讀取操作進(jìn)行極致優(yōu)化,同時(shí)稍微降低寫操作的速度。

假設(shè)這是你的使用情況,你可以創(chuàng)建更快的代碼。使用讀寫鎖時(shí),我們有兩個(gè)鎖,一個(gè)讀鎖,如下圖所示。它允許多個(gè)線程通過,實(shí)際上是“自由競(jìng)爭(zhēng)”的。


一旦我們需要寫入變量,我們需要獲得寫鎖,如下圖所示。我們嘗試請(qǐng)求寫鎖,但仍有線程從變量中讀取,因此我們必須等待。


一旦所有線程完成讀取,所有讀取操作都會(huì)阻塞,寫入操作只能由一個(gè)線程執(zhí)行,如下圖所示。一旦釋放寫鎖,我們將回到第一張圖中的“自由競(jìng)爭(zhēng)”狀態(tài)。


這是一種強(qiáng)大的模式,我們可以利用它使集合變得更快。一個(gè)典型的同步列表非常慢。它同步所有的操作,包括讀和寫。我們有一個(gè)CopyOnWriteArrayList,它對(duì)于讀取操作非常快,但是任何寫入操作都很慢。

如果可以避免從方法中返回迭代器,你可以封裝列表操作并使用這個(gè)API。例如,在以下代碼中,我們將名字列表暴露為只讀,但是當(dāng)需要添加名字時(shí),我們使用寫鎖。這可以輕松超過synchronized列表的性能:

private final ReadWriteLock LOCK = new ReentrantReadWriteLock();

private Collection<String> listOfNames = new ArrayList<>();

public void addName(String name) {

???LOCK.writeLock().lock();

???try {

???????listOfNames.add(name);

???} finally {

???????LOCK.writeLock().unlock();

???}

}

public boolean isInList(String name) {

???LOCK.readLock().lock();

???try {

???????return listOfNames.contains(name);

???} finally {

???????LOCK.readLock().unlock();

???}

}


StampedLock

我們首先要理解StampedLock的是它不可重入。例如,我們有以下代碼塊:

synchronized void methodA() {

?????// …

?????methodB();

????// …

}

synchronized void methodB() {

?????// …

}


這個(gè)方案可行,因?yàn)閟ynchronized是可重入的。我們已經(jīng)持有鎖,所以從methodA()進(jìn)入methodB()不會(huì)阻塞。這在使用ReentrantLock時(shí)也同樣適用,只要我們使用相同的鎖或相同的synchronized對(duì)象。

StampedLock返回一個(gè)戳記(stamp),我們用它來釋放鎖。因此,它有一些限制,但它仍然非常快和強(qiáng)大。它也包括一個(gè)讀寫戳記,我們可以用它來保護(hù)共享資源。但ReadWriteReentrantLock不同的是,它允許我們升級(jí)鎖。為什么需要這樣做呢?

看一下之前的addName()方法...如果我用"Shai"兩次調(diào)用它會(huì)怎樣?

是的,我可以使用Set...但是為了這個(gè)練習(xí)的目的,讓我們假設(shè)我們需要一個(gè)列表...我可以使用ReadWriteReentrantLock編寫那個(gè)邏輯:

public void addName(String name) {

???LOCK.writeLock().lock();

???try {

???????if(!listOfNames.contains(name)) {

???????????listOfNames.add(name);

???????}

???} finally {

???????LOCK.writeLock().unlock();

???}

}


這很糟糕。我“付出”寫鎖只是為了在某些情況下檢查contains()(假設(shè)有很多重復(fù)項(xiàng))。我們可以在獲取寫鎖之前調(diào)用isInList(name)。然后我們會(huì):

  • 獲取讀鎖

  • 釋放讀鎖

  • 獲取寫鎖

  • 釋放寫鎖

在兩種情況下,我們可能會(huì)排隊(duì),?這樣可能會(huì)增加額外的麻煩,不一定值得。

有了StampedLock,我們可以將讀鎖更新為寫鎖,并在需要的情況下立即進(jìn)行更改,例如:

public void addName(String name) {

???long stamp = LOCK.readLock();

???try {

???????if(!listOfNames.contains(name)) {

???????????long writeLock = LOCK.tryConvertToWriteLock(stamp);

???????????if(writeLock == 0) {

???????????????throw new IllegalStateException();

???????????}

???????????listOfNames.add(name);

???????}

???} finally {

???????LOCK.unlock(stamp);

???}

}


這是針對(duì)這些情況的一個(gè)強(qiáng)大的優(yōu)化。

終論

我經(jīng)常不假思索地使用 synchronized 集合,這有時(shí)可能是合理的,但對(duì)于大多數(shù)情況來說,這可能是次優(yōu)的。通過花費(fèi)一點(diǎn)時(shí)間研究與線程相關(guān)的原語,我們可以顯著提高性能。特別是在處理 Loom 時(shí),其中底層爭(zhēng)用更為敏感。想象一下在 100 萬并發(fā)線程上擴(kuò)展讀取操作的情況...在這些情況下,減少鎖爭(zhēng)用的重要性要大得多。

你可能會(huì)想,為什么?synchronized?集合不能使用?ReadWriteReentrantLock?或者是?StampedLock?呢?

這是一個(gè)問題,因?yàn)锳PI的可見接口范圍非常大,很難針對(duì)通用用例進(jìn)行優(yōu)化。這就是控制低級(jí)原語的地方,可以使高吞吐量和阻塞代碼之間的差異。

【注】本文譯自: Relearning Java Thread Primitives - DZone(
https://dzone.com/articles/relearning-java-thread-primitives)


重新學(xué)習(xí)Java線程原語的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
云林县| 天峨县| 通化县| 天峨县| 大名县| 兴安盟| 综艺| 乌审旗| 金川县| 南安市| 宣威市| 凤冈县| 绩溪县| 工布江达县| 新沂市| 来安县| 双桥区| 屏东县| 沿河| 苗栗市| 内江市| 长泰县| 来安县| 沈阳市| 鸡泽县| 汕尾市| 水城县| 余庆县| 婺源县| 德钦县| 龙州县| 三台县| 新密市| 曲阜市| 龙门县| 上饶市| 南岸区| 从化市| 乐都县| 兴海县| 芦溪县|