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

歡迎訪問(wèn) 生活随笔!

生活随笔

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

编程问答

关于多线程编程您不知道的 5 件事 有关高性能线程处理的微妙之处

發(fā)布時(shí)間:2025/3/8 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 关于多线程编程您不知道的 5 件事 有关高性能线程处理的微妙之处 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

雖然很少有 Java? 開(kāi)發(fā)人員能夠忽視多線程編程和支持它的 Java 平臺(tái)庫(kù),更少有人有時(shí)間深入研究線程。相反地,我們臨時(shí)學(xué)習(xí)線程,在需要時(shí)向我們的工具箱添加新的技巧和技術(shù)。以這種方式構(gòu)建和運(yùn)行適當(dāng)?shù)膽?yīng)用程序是可行的,但是您可以做的不止這些。理解 Java 編譯器的線程處理特性和 JVM 將有助于您編寫(xiě)更高效、性能更好的 Java 代碼。

在這期的?5 件事?系列?中,我將通過(guò)同步方法、volatile 變量和原子類(lèi)介紹多線程編程的一些更隱晦的方面。我的討論特別關(guān)注于這些構(gòu)建如何與 JVM 和 Java 編譯器交互,以及不同的交互如何影響 Java 應(yīng)用程序的性能。

1. 同步方法或同步代碼塊?

您可能偶爾會(huì)思考是否要同步化這個(gè)方法調(diào)用,還是只同步化該方法的線程安全子集。在這些情況下,知道 Java 編譯器何時(shí)將源代碼轉(zhuǎn)化為字節(jié)代碼會(huì)很有用,它處理同步方法和同步代碼塊的方式完全不同。

當(dāng) JVM 執(zhí)行一個(gè)同步方法時(shí),執(zhí)行中的線程識(shí)別該方法的?method_info?結(jié)構(gòu)是否有?ACC_SYNCHRONIZED?標(biāo)記設(shè)置,然后它自動(dòng)獲取對(duì)象的鎖,調(diào)用方法,最后釋放鎖。如果有異常發(fā)生,線程自動(dòng)釋放鎖。

另一方面,同步化一個(gè)方法塊會(huì)越過(guò) JVM 對(duì)獲取對(duì)象鎖和異常處理的內(nèi)置支持,要求以字節(jié)代碼顯式寫(xiě)入功能。如果您使用同步方法讀取一個(gè)方法的字節(jié)代碼,就會(huì)看到有十幾個(gè)額外的操作用于管理這個(gè)功能。清單 1 展示用于生成同步方法和同步代碼塊的調(diào)用:


清單 1. 兩種同步化方法

package com.geekcap;public class SynchronizationExample {private int i;public synchronized int synchronizedMethodGet() {return i;}public int synchronizedBlockGet() {synchronized( this ) {return i;}} }

synchronizedMethodGet()?方法生成以下字節(jié)代碼:

0: aload_01: getfield2: nop3: iconst_m14: ireturn

這里是來(lái)自?synchronizedBlockGet()?方法的字節(jié)代碼:

0: aload_01: dup2: astore_13: monitorenter4: aload_05: getfield6: nop7: iconst_m18: aload_19: monitorexit10: ireturn11: astore_212: aload_113: monitorexit14: aload_215: athrow

創(chuàng)建同步代碼塊產(chǎn)生了 16 行的字節(jié)碼,而創(chuàng)建同步方法僅產(chǎn)生了 5 行。

2. ThreadLocal 變量

如果您想為一個(gè)類(lèi)的所有實(shí)例維持一個(gè)變量的實(shí)例,將會(huì)用到靜態(tài)類(lèi)成員變量。如果您想以線程為單位維持一個(gè)變量的實(shí)例,將會(huì)用到線程局部變量。ThreadLocal?變量與常規(guī)變量的不同之處在于,每個(gè)線程都有其各自初始化的變量實(shí)例,這通過(guò)?get()?或?set()?方法予以評(píng)估。

比方說(shuō)您在開(kāi)發(fā)一個(gè)多線程代碼跟蹤器,其目標(biāo)是通過(guò)您的代碼惟一標(biāo)識(shí)每個(gè)線程的路徑。挑戰(zhàn)在于,您需要跨多個(gè)線程協(xié)調(diào)多個(gè)類(lèi)中的多個(gè)方法。如果沒(méi)有?ThreadLocal,這會(huì)是一個(gè)復(fù)雜的問(wèn)題。當(dāng)一個(gè)線程開(kāi)始執(zhí)行時(shí),它需要生成一個(gè)惟一的令牌來(lái)在跟蹤器中識(shí)別它,然后將這個(gè)惟一的令牌傳遞給跟蹤中的每個(gè)方法。

使用?ThreadLocal,事情就變得簡(jiǎn)單多了。線程在開(kāi)始執(zhí)行時(shí)初始化線程局部變量,然后通過(guò)每個(gè)類(lèi)的每個(gè)方法訪問(wèn)它,保證變量將僅為當(dāng)前執(zhí)行的線程托管跟蹤信息。在執(zhí)行完成之后,線程可以將其特定的蹤跡傳遞給一個(gè)負(fù)責(zé)維護(hù)所有跟蹤的管理對(duì)象。

當(dāng)您需要以線程為單位存儲(chǔ)變量實(shí)例時(shí),使用?ThreadLocal?很有意義。

3. Volatile 變量

我估計(jì),大約有一半的 Java 開(kāi)發(fā)人員知道 Java 語(yǔ)言包含?volatile?關(guān)鍵字。當(dāng)然,其中只有 10% 知道它的確切含義,有更少的人知道如何有效使用它。簡(jiǎn)言之,使用?volatile?關(guān)鍵字識(shí)別一個(gè)變量,意味著這個(gè)變量的值會(huì)被不同的線程修改。要完全理解?volatile關(guān)鍵字的作用,首先應(yīng)當(dāng)理解線程如何處理非易失性變量。

為了提高性能,Java 語(yǔ)言規(guī)范允許 JRE 在引用變量的每個(gè)線程中維護(hù)該變量的一個(gè)本地副本。您可以將變量的這些 “線程局部” 副本看作是與緩存類(lèi)似,在每次線程需要訪問(wèn)變量的值時(shí)幫助它避免檢查主存儲(chǔ)器。

不過(guò)看看在下面場(chǎng)景中會(huì)發(fā)生什么:兩個(gè)線程啟動(dòng),第一個(gè)線程將變量 A 讀取為 5,第二個(gè)線程將變量 A 讀取為 10。如果變量 A 從 5 變?yōu)?10,第一個(gè)線程將不會(huì)知道這個(gè)變化,因此會(huì)擁有錯(cuò)誤的變量 A 的值。但是如果將變量 A 標(biāo)記為?volatile,那么不管線程何時(shí)讀取 A 的值,它都會(huì)回頭查閱 A 的原版拷貝并讀取當(dāng)前值。

如果應(yīng)用程序中的變量將不發(fā)生變化,那么一個(gè)線程局部緩存比較行得通。不然,知道?volatile?關(guān)鍵字能為您做什么會(huì)很有幫助。

4. 易失性變量與同步化

如果一個(gè)變量被聲明為?volatile,這意味著它預(yù)計(jì)會(huì)由多個(gè)線程修改。當(dāng)然,您會(huì)希望 JRE 會(huì)為易失性變量施加某種形式的同步。幸運(yùn)的是,JRE 在訪問(wèn)易失性變量時(shí)確實(shí)隱式地提供同步,但是有一條重要提醒:讀取易失性變量是同步的,寫(xiě)入易失性變量也是同步的,但非原子操作不同步。

這表示下面的代碼不是線程安全的:

myVolatileVar++;

上一條語(yǔ)句也可寫(xiě)成:

int temp = 0; synchronize( myVolatileVar ) {temp = myVolatileVar; }temp++;synchronize( myVolatileVar ) {myVolatileVar = temp; }

換言之,如果一個(gè)易失性變量得到更新,這樣其值就會(huì)在底層被讀取、修改并分配一個(gè)新值,結(jié)果將是一個(gè)在兩個(gè)同步操作之間執(zhí)行的非線程安全操作。然后您可以決定是使用同步化還是依賴于 JRE 的支持來(lái)自動(dòng)同步易失性變量。更好的方法取決于您的用例:如果分配給易失性變量的值取決于當(dāng)前值(比如在一個(gè)遞增操作期間),要想該操作是線程安全的,那么您必須使用同步化。

5. 原子字段更新程序

在一個(gè)多線程環(huán)境中遞增或遞減一個(gè)原語(yǔ)類(lèi)型時(shí),使用在?java.util.concurrent.atomic?包中找到的其中一個(gè)新原子類(lèi)比編寫(xiě)自己的同步代碼塊要好得多。原子類(lèi)確保某些操作以線程安全方式被執(zhí)行,比如遞增和遞減一個(gè)值,更新一個(gè)值,添加一個(gè)值。原子類(lèi)列表包括?AtomicInteger、AtomicBoolean、AtomicLong、AtomicIntegerArray?等等。

使用原子類(lèi)的難題在于,所有類(lèi)操作,包括?get、set?和一系列?get-set?操作是以原子態(tài)呈現(xiàn)的。這表示,不修改原子變量值的?read和?write?操作是同步的,不僅僅是重要的?read-update-write?操作。如果您希望對(duì)同步代碼的部署進(jìn)行更多細(xì)粒度控制,那么解決方案就是使用一個(gè)原子字段更新程序。

使用原子更新

像?AtomicIntegerFieldUpdater、AtomicLongFieldUpdater?和?AtomicReferenceFieldUpdater?之類(lèi)的原子字段更新程序基本上是應(yīng)用于易失性字段的封裝器。Java 類(lèi)庫(kù)在內(nèi)部使用它們。雖然它們沒(méi)有在應(yīng)用程序代碼中得到廣泛使用,但是也沒(méi)有不能使用它們的理由。

清單 2 展示一個(gè)有關(guān)類(lèi)的示例,該類(lèi)使用原子更新來(lái)更改某人正在讀取的書(shū)目:


清單 2. Book 類(lèi)

package com.geeckap.atomicexample;public class Book {private String name;public Book(){}public Book( String name ){this.name = name;}public String getName(){return name;}public void setName( String name ){this.name = name;} }

Book?類(lèi)僅是一個(gè) POJO(Java 原生類(lèi)對(duì)象),擁有一個(gè)單一字段:name。


清單 3. MyObject 類(lèi)

package com.geeckap.atomicexample;import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;/**** @author shaines*/ public class MyObject {private volatile Book whatImReading;private static final AtomicReferenceFieldUpdater<MyObject,Book> updater =AtomicReferenceFieldUpdater.newUpdater( MyObject.class, Book.class, "whatImReading" );public Book getWhatImReading(){return whatImReading;}public void setWhatImReading( Book whatImReading ){//this.whatImReading = whatImReading;updater.compareAndSet( this, this.whatImReading, whatImReading );} }

正如您所期望的,清單 3?中的?MyObject?類(lèi)通過(guò)?get?和?set?方法公開(kāi)其?whatAmIReading?屬性,但是?set?方法所做的有點(diǎn)不同。它不僅僅將其內(nèi)部?Book?引用分配給指定的?Book(這將使用?清單 3?中注釋出的代碼來(lái)完成),而是使用一個(gè)AtomicReferenceFieldUpdater。

AtomicReferenceFieldUpdater

AtomicReferenceFieldUpdater?的 Javadoc 將其定義為:

對(duì)指定類(lèi)的指定易失性引用字段啟用原子更新的一個(gè)基于映像的實(shí)用程序。該類(lèi)旨在用于這樣的一個(gè)原子數(shù)據(jù)結(jié)構(gòu)中:即同一節(jié)點(diǎn)的若干引用字段獨(dú)立地得到原子更新。

在?清單 3?中,AtomicReferenceFieldUpdater?由一個(gè)對(duì)其靜態(tài)?newUpdater?方法的調(diào)用創(chuàng)建,該方法接受三個(gè)參數(shù):

  • 包含字段的對(duì)象的類(lèi)(在本例中為?MyObject)
  • 將得到原子更新的對(duì)象的類(lèi)(在本例中是?Book)
  • 將經(jīng)過(guò)原子更新的字段的名稱(chēng)

這里真正的價(jià)值在于,getWhatImReading?方法未經(jīng)任何形式的同步便被執(zhí)行,而?setWhatImReading?是作為一個(gè)原子操作執(zhí)行的。

清單 4 展示如何使用?setWhatImReading()?方法并斷定值的變動(dòng)是正確的:


清單 4. 演習(xí)原子更新的測(cè)試用例

package com.geeckap.atomicexample;import org.junit.Assert; import org.junit.Before; import org.junit.Test;public class AtomicExampleTest {private MyObject obj;@Beforepublic void setUp(){obj = new MyObject();obj.setWhatImReading( new Book( "Java 2 From Scratch" ) );}@Testpublic void testUpdate(){obj.setWhatImReading( new Book( "Pro Java EE 5 Performance Management and Optimization" ) );Assert.assertEquals( "Incorrect book name", "Pro Java EE 5 Performance Management and Optimization", obj.getWhatImReading().getName() );}}

參閱?參考資料?了解有關(guān)原子類(lèi)的更多信息。

結(jié)束語(yǔ)

多線程編程永遠(yuǎn)充滿了挑戰(zhàn),但是隨著 Java 平臺(tái)的演變,它獲得了簡(jiǎn)化一些多線程編程任務(wù)的支持。在本文中,我討論了關(guān)于在 Java 平臺(tái)上編寫(xiě)多線程應(yīng)用程序您可能不知道的 5 件事,包括同步化方法與同步化代碼塊之間的不同,為每個(gè)線程存儲(chǔ)運(yùn)用ThreadLocal?變量的價(jià)值,被廣泛誤解的?volatile?關(guān)鍵字(包括依賴于?volatile?滿足同步化需求的危險(xiǎn)),以及對(duì)原子類(lèi)的錯(cuò)雜之處的一個(gè)簡(jiǎn)要介紹。參見(jiàn)?參考資料?部分了解更多內(nèi)容。

參考資料

學(xué)習(xí)

  • 您不知道的 5 件事?...?:在本系列中發(fā)現(xiàn)關(guān)于 Java 平臺(tái)您不知道的事情,本系列致力于將 Java 技術(shù)瑣事變成有用的編程技巧。

  • “Code Tracing”(Steven Haines,InformIT,2010 年 8 月):了解使用?ThreadLocal?變量進(jìn)行代碼跟蹤的更多內(nèi)容。

  • “Java 字節(jié)碼:了解字節(jié)碼使你成為一個(gè)更好的程序員”(Peter Haggar,developerWorks,2001 年 7 月):一本介紹字節(jié)碼次要領(lǐng)域的教程,包含展示同步方法和同步代碼塊之間區(qū)別的一個(gè)較早的例子。

  • “Java 理論與實(shí)踐:流行的原子”(Brian Goetz,developerWorks,2004 年 11 月):解釋原子類(lèi)如何支持用 Java 語(yǔ)言開(kāi)發(fā)高度可伸縮的非阻塞算法。

  • “Java 理論與實(shí)踐: 并發(fā)在一定程度上使一切變得簡(jiǎn)單”(Brian Goetz,developerWorks,2002 年 11 月): 通過(guò)java.util.concurrent?包來(lái)為您提供指導(dǎo)。

  • “您不知道的 5 件事?... java.util.concurrent,第 1 部分”(Ted Neward,developerWorks,2010 年 5 月):了解 5 個(gè)并發(fā)集合類(lèi),為您的并發(fā)編程需求改進(jìn)標(biāo)準(zhǔn)集合類(lèi)。

  • developerWorks Java technology 專(zhuān)區(qū):這里有數(shù)百篇關(guān)于 Java 編程各個(gè)方面的文章。

討論

  • 加入?developerWorks 中文社區(qū)。

關(guān)于作者

Steven Haines 是 ioko 的一名技術(shù)架構(gòu)師,也是 GeekCap Inc 的創(chuàng)始人。在 Java 編程和性能分析方面,他寫(xiě)過(guò) 3 本書(shū),以及上百篇文章和十幾個(gè)白皮書(shū)。Steven 還在行業(yè)會(huì)議上發(fā)表演講,比如 JBoss World 和 STPCon,而且他曾在加里佛尼亞大學(xué)歐文分校和 Learning Tree 大學(xué)教過(guò) Java 編程,他居住在佛羅里達(dá)州奧蘭多市。

建議

總結(jié)

以上是生活随笔為你收集整理的关于多线程编程您不知道的 5 件事 有关高性能线程处理的微妙之处的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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