并发最佳实践
本文是我們名為“ 高級Java ”的學(xué)院課程的一部分。
本課程旨在幫助您最有效地使用Java。 它討論了高級主題,包括對象創(chuàng)建,并發(fā),序列化,反射等。 它將指導(dǎo)您完成Java掌握的過程! 在這里查看 !
目錄
1.簡介 2.線程和線程組 3.并發(fā),同步和不變性 4.期貨,執(zhí)行人和線程池 5.鎖 6.線程調(diào)度器 7.原子操作 8.并行收集 9.探索Java標(biāo)準(zhǔn)庫 10.明智地使用同步 11.等待/通知 12.對并發(fā)問題進(jìn)行故障排除 13.接下來是什么 14.下載1.簡介
多處理器和多核硬件體系結(jié)構(gòu)極大地影響了當(dāng)今運(yùn)行在其上的應(yīng)用程序的設(shè)計(jì)和執(zhí)行模型。 為了充分利用可用計(jì)算單元的全部功能,應(yīng)用程序應(yīng)準(zhǔn)備好支持同時運(yùn)行并爭奪資源和內(nèi)存的多個執(zhí)行流。 并發(fā)編程帶來了許多與數(shù)據(jù)訪問和事件的不確定性相關(guān)的挑戰(zhàn),這些挑戰(zhàn)可能導(dǎo)致意外崩潰和奇怪的故障。
在本教程的這一部分中,我們將研究Java可以為開發(fā)人員提供什么,以幫助他們在并發(fā)世界中編寫健壯而安全的應(yīng)用程序。
2.線程和線程組
線程是Java中并發(fā)應(yīng)用程序的基礎(chǔ)構(gòu)建塊。 線程有時被稱為輕量級進(jìn)程 ,它們允許多個執(zhí)行流并發(fā)進(jìn)行。 Java中的每個應(yīng)用程序都有至少一個稱為主線程的線程 。 每個Java線程僅存在于JVM內(nèi)部,并且可能不反映任何操作系統(tǒng)線程。
Java中的Thread是Thread類的實(shí)例。 通常,不建議使用Thread類的實(shí)例直接創(chuàng)建和管理線程( Futures和Executors部分中介紹的執(zhí)行器和線程池提供了一種更好的方法),但是這樣做非常容易:
public static void main(String[] args) {new Thread( new Runnable() {@Overridepublic void run() {// Some implementation here}} ).start(); }或使用Java 8 lambda函數(shù)的相同示例:
public static void main(String[] args) {new Thread( () -> { /* Some implementation here */ } ).start(); }但是,用Java創(chuàng)建新線程看起來非常簡單,線程具有復(fù)雜的生命周期,并且可以處于以下狀態(tài)之一(在給定的時間點(diǎn),線程只能處于一種狀態(tài))。
| 線程狀態(tài) | 描述 |
| NEW | 尚未啟動的線程處于此狀態(tài)。 |
| RUNNABLE | 在Java虛擬機(jī)中執(zhí)行的線程處于這種狀態(tài)。 |
| BLOCKED | 等待監(jiān)視器鎖定而被阻塞的線程處于此狀態(tài)。 |
| WAITING | 無限期地等待另一個線程執(zhí)行特定操作的線程處于此狀態(tài)。 |
| TIMED_WAITING | 無限期地等待另一個線程執(zhí)行特定操作的線程處于此狀態(tài)。 |
| TERMINATED | 退出的線程處于此狀態(tài)。 |
表格1
目前并不是所有的線程狀態(tài)都明確,但是在本教程的后面,我們將介紹其中的大多數(shù)內(nèi)容,并討論導(dǎo)致線程處于一種或另一種狀態(tài)的事件類型。
線程可以組裝成組。 線程組代表一組線程,并且還可以包括其他線程組(因此形成樹)。 線程組旨在成為一個不錯的功能,但是由于執(zhí)行程序和線程池(請參閱Futures,Executor和Thread Pools )是更好的替代方法,因此如今不建議使用它們。
3.并發(fā),同步和不變性
在大多數(shù)每個Java應(yīng)用程序中,都需要多個運(yùn)行線程相互通信并訪問共享數(shù)據(jù)。 讀取這些數(shù)據(jù)并不是什么大問題,但是對其進(jìn)行不協(xié)調(diào)的修改將直接導(dǎo)致災(zāi)難(所謂的賽車狀況)。 這就是英寸同步 同步踢是確保在同一時間幾個同時運(yùn)行的線程將不執(zhí)行的應(yīng)用程序代碼的具體守衛(wèi)(同步)塊中的機(jī)構(gòu)的點(diǎn)。 如果其中一個線程已開始執(zhí)行代碼的同步塊,則任何其他試圖執(zhí)行同一塊的線程都必須等待,直到第一個線程完成。
Java語言具有內(nèi)置的同步支持,形式為synchronized關(guān)鍵字。 該關(guān)鍵字可以應(yīng)用于實(shí)例方法,靜態(tài)方法,也可以在任意執(zhí)行塊周圍使用,并確保一次僅一個線程將能夠調(diào)用它。 例如:
public synchronized void performAction() {// Some implementation here }public static synchronized void performClassAction() {// Some implementation here }或者,使用與代碼塊同步的示例:
public void performActionBlock() {synchronized( this ) {// Some implementation here} }synchronized關(guān)鍵字還有另一個非常重要的作用:它會為同一對象的synchronized方法或代碼塊的任何調(diào)用自動建立先發(fā)生的關(guān)系( http://en.wikipedia.org/wiki/Happened-before )。 這保證了對象狀態(tài)的更改對所有線程都是可見的。
請注意,構(gòu)造函數(shù)無法同步(將synchronized關(guān)鍵字與構(gòu)造函數(shù)一起使用會引起編譯器錯誤),因?yàn)樵跇?gòu)造實(shí)例時,只有創(chuàng)建實(shí)例的線程才能訪問它。
在Java中,同步是圍繞稱為監(jiān)視器的內(nèi)部實(shí)體(或固有/監(jiān)視器鎖定, http://en.wikipedia.org/wiki/Monitor_ (synchronization))構(gòu)建的。 Monitor強(qiáng)制對對象狀態(tài)進(jìn)行獨(dú)占訪問,并建立事前關(guān)聯(lián)。 當(dāng)任何線程調(diào)用synchronized方法時,它將自動獲取該方法實(shí)例(或靜態(tài)方法中的類)的內(nèi)在(監(jiān)視)鎖,并在方法返回時釋放它。
最后,同步是Java可重入的 :這意味著線程可以獲取它已經(jīng)擁有的鎖。 由于線程具有較少的阻塞自身的機(jī)會,因此重新進(jìn)入可大大簡化并發(fā)應(yīng)用程序的編程模型。
如您所見,并發(fā)在Java應(yīng)用程序中引入了很多復(fù)雜性。 但是,有一種解決方法: 不變性 。 我們已經(jīng)討論過很多次了,但是對于多線程應(yīng)用程序來說,這確實(shí)非常重要:不可變對象不需要同步,因?yàn)樗鼈冇肋h(yuǎn)不會被多個線程更新。
4.期貨,執(zhí)行人和線程池
用Java創(chuàng)建新線程很容易,但是管理它們確實(shí)很困難。 Java標(biāo)準(zhǔn)庫以執(zhí)行程序和線程池的形式提供了極其有用的抽象,旨在簡化線程管理。
本質(zhì)上,在其最簡單的實(shí)現(xiàn)中,線程池創(chuàng)建并維護(hù)線程列表,這些線程列表可立即使用。 應(yīng)用程序無需每次都生成新線程,而只是從池中借用一個(或所需的多個線程)。 一旦借用的線程完成其工作,它將返回到池中,并可以用來接管下一個任務(wù)。
盡管可以直接使用線程池,但是Java標(biāo)準(zhǔn)庫提供了執(zhí)行程序外觀,該外觀具有一組工廠方法來創(chuàng)建常用的線程池配置。 例如,下面的代碼段創(chuàng)建了一個具有固定線程數(shù)(10)的線程池:
ExecutorService executor = Executors.newFixedThreadPool( 10 );執(zhí)行程序可以用來卸載任何任務(wù),因此它將在與線程池分開的線程中執(zhí)行(注意,不建議將執(zhí)行程序用于長時間運(yùn)行的任務(wù))。 執(zhí)行程序的外觀允許自定義基礎(chǔ)線程池的行為,并支持以下配置:
| 方法 | 描述 |
| Executors.newCachedThreadPool | 創(chuàng)建一個線程池,該線程池根據(jù)需要創(chuàng)建新線程,但是將在先前構(gòu)造的線程可用時重用它們。 |
| Executors.newFixedThreadPool | 創(chuàng)建一個線程池,該線程池重用在共享的無邊界隊(duì)列上運(yùn)行的固定數(shù)量的線程。 |
| Executors.newScheduledThreadPool | 創(chuàng)建一個線程池,該線程池可以安排命令在給定的延遲后運(yùn)行或定期執(zhí)行。 |
| Executors.newSingleThreadExecutor | 創(chuàng)建一個執(zhí)行程序,該執(zhí)行程序使用在不受限制的隊(duì)列上操作的單個工作線程。 |
| Executors.newSingleThreadScheduledExecutor | 創(chuàng)建一個單線程執(zhí)行器,該執(zhí)行器可以計(jì)劃命令在給定的延遲后運(yùn)行或定期執(zhí)行。 |
表2
在某些情況下,執(zhí)行的結(jié)果不是很重要,因此執(zhí)行程序支持即發(fā)即棄的語義,例如:
executor.execute( new Runnable() { @Overridepublic void run() {// Some implementation here} } );等效的Java 8示例更加簡潔:
executor.execute( () -> {// Some implementation here } );但是,如果執(zhí)行的結(jié)果很重要,則Java標(biāo)準(zhǔn)庫會提供另一個抽象來表示將來會發(fā)生的計(jì)算,稱為Future<T> 。 例如:
Future< Long > result = executor.submit( new Callable< Long >() {@Overridepublic Long call() throws Exception {// Some implementation herereturn ...;} } );Future<T>的結(jié)果可能無法立即獲得,因此應(yīng)用程序應(yīng)使用一系列g(shù)et(…)方法來等待它。 例如:
Long value = result.get( 1, TimeUnit.SECONDS );如果計(jì)算結(jié)果在指定的超時時間內(nèi)不可用,則將引發(fā)TimeoutException異常。 有一個重載版本的get()會一直等待,但是請您優(yōu)先使用超時的版本。
自Java 8發(fā)行以來,開發(fā)人員擁有Future<T>另一個版本CompletableFuture<T> ,該版本支持在其完成時觸發(fā)的附加功能和操作。 不僅如此,通過引入流,Java 8引入了一種簡單,非常直接的方式來使用parallelStream()方法執(zhí)行并行收集處理,例如:
final Collection< String > strings = new ArrayList<>(); // Some implementation herefinal int sumOfLengths = strings.parallelStream().filter( str -> !str.isEmpty() ).mapToInt( str -> str.length() ).sum();執(zhí)行程序和并行流帶到Java平臺的簡單性使Java中的并行和并行編程變得更加容易。 但是有一個陷阱:線程池和并行流的不受控制的創(chuàng)建可能會破壞應(yīng)用程序的性能,因此,對它們進(jìn)行相應(yīng)的管理很重要。
5.鎖
除了監(jiān)視器之外,Java還支持可重入互斥鎖(具有與監(jiān)視器鎖相同的基本行為和語義,但具有更多功能)。 這些鎖可通過java.util.concurrent.locks包中的ReentrantLock類獲得。 這是一個典型的鎖用法習(xí)慣用法:
private final ReentrantLock lock = new ReentrantLock();public void performAction() {lock.lock();try { // Some implementation here} finally {lock.unlock();} }請注意,必須通過調(diào)用unlock()方法顯式地釋放任何鎖(對于synchronized方法和執(zhí)行塊,Java編譯器在內(nèi)部發(fā)出釋放監(jiān)視器鎖的指令)。 如果鎖需要編寫更多的代碼,為什么它們比監(jiān)視器更好? 好吧,出于幾個原因,但最重要的是,鎖可以在等待獲取時使用超時并快速失敗(監(jiān)控器始終無限期地等待,并且無法指定所需的超時)。 例如:
public void performActionWithTimeout() throws InterruptedException {if( lock.tryLock( 1, TimeUnit.SECONDS ) ) {try {// Some implementation here} finally {lock.unlock();}} }現(xiàn)在,當(dāng)我們對監(jiān)視器和鎖有了足夠的了解時,讓我們討論它們的使用如何影響線程狀態(tài)。
當(dāng)任何線程正在使用lock()方法調(diào)用等待鎖(由另一個線程獲取lock() ,它處于WAITING狀態(tài)。 但是,當(dāng)任何線程使用帶有超時的tryLock()方法調(diào)用等待鎖(由另一個線程獲取tryLock()時,它處于TIMED_WAITING狀態(tài)。 相反,當(dāng)任何線程正在使用synchronized方法或執(zhí)行塊等待監(jiān)視器(由另一個線程獲取)時,它處于BLOCKED狀態(tài)。
到目前為止,我們看到的示例非常簡單,但是鎖管理確實(shí)很困難,而且充滿陷阱。 其中最臭名昭著的是僵局:兩個或兩個以上競爭線程正在互相等待進(jìn)行操作,因此從來沒有這樣做的情況。 當(dāng)涉及多個鎖或監(jiān)視器鎖時,通常會發(fā)生死鎖。 JVM通常能夠檢測正在運(yùn)行的應(yīng)用程序中的死鎖并警告開發(fā)人員(請參閱“并發(fā)問題疑難解答”部分)。 僵局的典型示例如下所示:
private final ReentrantLock lock1 = new ReentrantLock(); private final ReentrantLock lock2 = new ReentrantLock();public void performAction() {lock1.lock();try {// Some implementation here try {lock2.lock(); // Some implementation here} finally {lock2.unlock();} // Some implementation here} finally {lock1.unlock();} }public void performAnotherAction() {lock2.lock();try {// Some implementation here try {lock1.lock(); // Some implementation here} finally {lock1.unlock();} // Some implementation here} finally {lock2.unlock();} }performAction()方法嘗試先獲取lock1 ,然后獲取lock2 ,而performAnotherAction()方法lock2不同的順序lock2它, lock2 ,再獲取lock1 。 如果通過程序執(zhí)行流在兩個不同線程中的同一類實(shí)例上調(diào)用這兩個方法,則很可能發(fā)生死鎖:第一個線程將無限期地等待第二個線程獲取的lock2 ,而第二個線程將無限期等待無限期地等待第一個獲得的lock1 。
6.線程調(diào)度器
在JVM中,線程調(diào)度程序確定應(yīng)運(yùn)行哪個線程以及運(yùn)行多長時間。 Java應(yīng)用程序創(chuàng)建的所有線程都具有優(yōu)先級,該優(yōu)先級在決定何時調(diào)度線程及其時間范圍時,會基本上影響線程調(diào)度算法。 但是,此功能的聲譽(yù)是不可移植的(因?yàn)榇蠖鄶?shù)技巧都依賴于線程調(diào)度程序的特定行為)。
Thread類還通過使用yield()方法提供了另一種干預(yù)線程調(diào)度實(shí)現(xiàn)的方法。 它暗示線程調(diào)度程序當(dāng)前線程愿意放棄其當(dāng)前使用的處理器時間(并且還具有不可移植的聲譽(yù))。
總的來說,依靠Java線程調(diào)度程序的實(shí)現(xiàn)細(xì)節(jié)并不是一個好主意。 這就是為什么Java標(biāo)準(zhǔn)庫中的執(zhí)行者和線程池(請參閱“ 期貨,執(zhí)行者和線程池”部分)試圖不向開發(fā)人員公開那些不可移植的細(xì)節(jié)(但如果確實(shí)有必要的話,仍然可以這樣做) )。 沒有什么比精心設(shè)計(jì)更好地工作了,精心設(shè)計(jì)試圖考慮應(yīng)用程序所運(yùn)行的實(shí)際硬件(例如,使用Runtime類可以輕松檢索可用的CPU和內(nèi)核數(shù)量)。
7.原子操作
在多線程世界中,有一組特定的指令稱為比較交換 (CAS)。 這些指令將它們的值與給定值進(jìn)行比較,并且只有它們相同時,才設(shè)置新的給定值。 這是通過單個原子操作完成的,該操作通常是無鎖且高效的。
Java標(biāo)準(zhǔn)庫中有大量支持原子操作的類列表,所有這些類都位于java.util.concurrent.atomic包下。
| 類 | 描述 |
| AtomicBoolean | 可以自動更新的布爾值 |
| AtomicInteger | 一個可以自動更新的int值。 |
| AtomicIntegerArray | 一個可能會自動更新的長值。 |
| AtomicLongArray | 一個長數(shù)組,其中的元素可以原子更新。 |
| AtomicReference<V> | 可以原子更新的對象引用。 |
| AtomicReferenceArray<E> | 對象引用的數(shù)組,其中的元素可以原子更新。 |
表3
Java 8版本通過一組新的原子操作(累加器和加法器)擴(kuò)展了java.util.concurrent.atomic 。
| 類 | 描述 |
| DoubleAccumulator | 一個或多個變量一起保持使用提供的函數(shù)更新的運(yùn)行雙精度值。 |
| DoubleAdder | 一個或多個變量共同保持初始為零的雙和。 |
| LongAccumulator | 一個或多個變量一起保持使用提供的函數(shù)更新的運(yùn)行時長值。 |
| LongAdder | 一個或多個變量共同保持初始為零的長和。 |
表4
8.并行收集
可以由多個線程訪問和修改的共享集合不是一個例外,而是一個規(guī)則。 Java標(biāo)準(zhǔn)庫在Collections類中提供了兩個有用的靜態(tài)方法,這些方法使任何現(xiàn)有的collection都是線程安全的。 例如:
final Set< String > strings = Collections.synchronizedSet( new HashSet< String >() );final Map< String, String > keys = Collections.synchronizedMap( new HashMap< String, String >() );但是,返回的通用集合包裝器是線程安全的,通常不是最好的選擇,因?yàn)樗鼈冊趯?shí)際應(yīng)用程序中的性能相當(dāng)中等。 這就是為什么Java標(biāo)準(zhǔn)庫包含一組針對并發(fā)調(diào)整的豐富的收集類的原因。 以下只是最廣泛使用的列表,所有列表都托管在java.util.concurrent包下。
| 類 | 描述 |
| ArrayBlockingQueue<E> | 由數(shù)組支持的有界阻塞隊(duì)列。 |
| ConcurrentHashMap<K,V> | 哈希表支持檢索的完全并發(fā)性和可更新的可調(diào)整預(yù)期并發(fā)性。 |
| ConcurrentLinkedDeque<E> | 基于鏈接節(jié)點(diǎn)的無限制并發(fā)雙端隊(duì)列。 |
| ConcurrentLinkedQueue<E> | 基于鏈接節(jié)點(diǎn)的無界線程安全隊(duì)列。 |
| ConcurrentSkipListMap<K,V> | 可擴(kuò)展的并發(fā)地圖實(shí)現(xiàn) |
| ConcurrentSkipListSet<E> | 基于ConcurrentSkipListMap可伸縮并發(fā)集實(shí)現(xiàn)。 |
| CopyOnWriteArrayList<E> | ArrayList的線程安全變體,其中所有可變操作(添加,設(shè)置等)都通過對基礎(chǔ)數(shù)組進(jìn)行全新復(fù)制來實(shí)現(xiàn)。 |
| CopyOnWriteArraySet<E> | 一個使用內(nèi)部CopyOnWriteArrayList進(jìn)行所有操作的Set。 |
| DelayQueue<E extends Delayed> | 延遲元素的無限制阻塞隊(duì)列,在該隊(duì)列中,僅當(dāng)元素的延遲到期時才可以使用該元素。 |
| LinkedBlockingDeque<E> | 基于鏈接節(jié)點(diǎn)的可選綁定的阻塞雙端隊(duì)列。 |
| LinkedBlockingQueue<E> | 基于鏈接節(jié)點(diǎn)的可選綁定的阻塞隊(duì)列。 |
| LinkedTransferQueue<E> | 基于鏈接節(jié)點(diǎn)的無界TransferQueue 。 |
| PriorityBlockingQueue<E> | 一個無界阻塞隊(duì)列,它使用與類PriorityQueue相同的排序規(guī)則,并提供阻塞檢索操作。 |
| SynchronousQueue<E> | 一個阻塞隊(duì)列,其中每個插入操作必須等待另一個線程進(jìn)行相應(yīng)的刪除操作,反之亦然。 |
表5
這些類是專門為在多線程應(yīng)用程序中使用而設(shè)計(jì)的。 他們利用許多技術(shù)來使對集合的并發(fā)訪問盡可能高效,并且是synchronized集合包裝器的推薦替代者。
9.探索Java標(biāo)準(zhǔn)庫
對于編寫并發(fā)應(yīng)用程序的Java開發(fā)人員來說, java.util.concurrent和java.util.concurrent.locks包是真正的瑰寶。 由于那里有很多類,因此在本節(jié)中,我們將介紹其中最有用的類,但是請不要猶豫地查閱Java官方文檔并進(jìn)行探索。
| 類 | 描述 |
| CountDownLatch | 一種同步幫助,允許一個或多個線程等待,直到在其他線程中執(zhí)行的一組操作完成為止。 |
| CyclicBarrier | 一種同步輔助工具,它允許一組線程全部互相等待以到達(dá)一個公共的障礙點(diǎn)。 |
| Exchanger<V> | 線程可以配對并在配對中交換元素的同步點(diǎn)。 |
| Phaser | 可重用的同步屏障,其功能類似于CyclicBarrier和CountDownLatch但支持更靈活的用法。 |
| Semaphore | 計(jì)數(shù)信號量。 |
| ThreadLocalRandom | 隔離到當(dāng)前線程的隨機(jī)數(shù)生成器 |
| ReentrantReadWriteLock | 讀/寫鎖的實(shí)現(xiàn) |
表6
不幸的是, ReentrantReadWriteLock的Java實(shí)現(xiàn)不是那么出色,從Java 8開始,有了新的鎖:
| 類 | 描述 |
| StampedLock | 一種基于功能的鎖,具有三種模式來控制讀/寫訪問。 |
表7
10.明智地使用同步
鎖定和synchronized關(guān)鍵字是功能強(qiáng)大的工具,可在多線程應(yīng)用程序中極大地幫助保持?jǐn)?shù)據(jù)模型和程序狀態(tài)的一致性。 但是,不明智地使用它們會導(dǎo)致線程爭用,并且可能會大大降低應(yīng)用程序性能。 另一方面,不使用同步原語可能(并且將)導(dǎo)致怪異的程序狀態(tài)和數(shù)據(jù)損壞,最終導(dǎo)致應(yīng)用程序崩潰。 因此,平衡很重要。
建議是在確實(shí)需要的地方嘗試使用鎖或/和synchronized 。 在執(zhí)行此操作時,請確保盡快釋放鎖定,并且將需要鎖定或同步的執(zhí)行塊保持在最小限度。 那些技術(shù)至少應(yīng)該有助于減少競爭,但不會消除競爭。
近年來,出現(xiàn)了許多所謂的無鎖算法和數(shù)據(jù)結(jié)構(gòu)(例如, Atomic Operations部分中的Java 原子操作 )。 與使用同步原語構(gòu)建的等效實(shí)現(xiàn)相比,它們提供了更好的性能。
很高興知道JVM有一些運(yùn)行時優(yōu)化,以消除可能不必要的鎖定。 最有名的是偏壓鎖定 :一種優(yōu)化,它通過消除與Java同步原語相關(guān)的操作來提高無競爭的同步性能(有關(guān)更多詳細(xì)信息,請參閱http://www.oracle.com/technetwork/java/6-performance-137236 .html#2.1.1 )。
不過,JVM會盡力而為,消除應(yīng)用程序中不必要的同步是更好的選擇。 過多使用同步會對應(yīng)用程序性能產(chǎn)生負(fù)面影響,因?yàn)榫€程將浪費(fèi)昂貴的CPU周期來爭奪資源,而不是進(jìn)行實(shí)際工作。
11.等待/通知
在Java標(biāo)準(zhǔn)庫( java.util.concurrent )中引入并發(fā)實(shí)用程序之前,使用Object的wait()/notify()/notifyAll()方法是在Java中的線程之間建立通信的方式。 僅當(dāng)線程擁有有關(guān)對象的監(jiān)視器時,才必須調(diào)用所有這些方法 。 例如:
private Object lock = new Object();public void performAction() {synchronized( lock ) {while( <condition> ) {// Causes the current thread to wait until// another thread invokes the notify() or notifyAll() methods.lock.wait();}// Some implementation here } }方法wait()釋放當(dāng)前線程持有的監(jiān)視器鎖定,因?yàn)樗形礉M足其等待的條件( wait()方法必須在循環(huán)中調(diào)用,并且絕不能在循環(huán)外部調(diào)用 )。 因此,在同一監(jiān)視器上等待的另一個線程有機(jī)會運(yùn)行。 完成此線程后,它應(yīng)調(diào)用notify()/notifyAll()方法之一來喚醒等待監(jiān)視器鎖定的一個或多個線程。 例如:
public void performAnotherAction() {synchronized( lock ) { // Some implementation here// Wakes up a single thread that is waiting on this object's monitor.lock.notify();} }notify()和notifyAll()之間的區(qū)別在于,第一個喚醒單個線程,而第二個喚醒所有等待的線程(它們開始爭奪監(jiān)視器鎖定)。
不建議在現(xiàn)代Java應(yīng)用程序中使用wait()/notify()習(xí)慣用法。 它不僅復(fù)雜,還需要遵循一組強(qiáng)制性規(guī)則。 因此,它可能會在正在運(yùn)行的程序中引起細(xì)微的錯誤,這將是非常困難且耗時的調(diào)查。 java.util.concurrent有很多可以用更簡單的替代方法來替換wait()/notify()方法(在現(xiàn)實(shí)情況下,它很可能會具有更好的性能)。
12.對并發(fā)問題進(jìn)行故障排除
在多線程應(yīng)用程序中,很多事情可能出錯。 復(fù)制問題成為噩夢。 調(diào)試和故障排除可能要花費(fèi)數(shù)小時甚至數(shù)天甚至數(shù)周。 Java開發(fā)工具包(JDK)包括幾個工具,這些工具至少能夠提供有關(guān)應(yīng)用程序線程及其狀態(tài)的一些詳細(xì)信息,并診斷死鎖條件(請參閱線程和線程組以及鎖部分)。 首先是一個好點(diǎn)。 這些工具是(但不限于):
- JVisualVM ( http://docs.oracle.com/javase/7/docs/technotes/tools/share/jvisualvm.html )
- Java任務(wù)控制 ( http://docs.oracle.com/javacomponents/jmc.htm )
- jstack ( https://docs.oracle.com/javase/7/docs/technotes/tools/share/jstack.html )
13.接下來是什么
在這一部分中,我們研究了現(xiàn)代軟件和硬件平臺的非常重要的方面-并發(fā)性。 特別是,我們已經(jīng)看到Java作為一種語言及其標(biāo)準(zhǔn)庫為開發(fā)人員提供了哪些工具,以幫助他們處理并發(fā)和異步執(zhí)行。 在本教程的下一部分中,我們將介紹Java中的序列化技術(shù)。
14.下載
您可以在此處下載本課程的源代碼: advanced-java-part-9
翻譯自: https://www.javacodegeeks.com/2015/09/concurrency-best-practices.html
總結(jié)
- 上一篇: 隐私设置错误怎么处理(隐私设置错误怎么处
- 下一篇: 小米9透明尊享版(小米9透明尊享版绝版了