Java並發之線程封閉


讀者們好! 在這篇博客中,我們將探討線程封閉是什么意思,以及我們如何實現它。 所以,讓我們直接開始吧。

1. 線程封閉

大多數的並發問題僅發生在我們想要在線程之間共享可變變量或可變狀態時。如果在多個線程之間操作共享變量,則所有線程都將能夠讀取和修改變量的值,從而出現意外或不正確的結果。一種簡單的避免此問題的方式是不在線程之間共享數據。 這種技術稱為線程封閉,是在我們的應用程序中實現線程安全的最簡單方法之一。

Java 語言本身沒有任何強制執行線程封閉的方法。線程封閉是通過不允許多個線程同時使用同一個狀態的方式的程序設計來實現的,因此由程序實現強制執行。 幾種類型的線程封閉,如下所示:

1.1 Ad-Hoc 線程封閉

Ad-hoc 線程封閉描述了線程封閉的方式,由開發人員或從事該項目的開發人員確保僅在單個線程內使用此對象。 這種方式方法可用性不高,在大多數情況下應該避免。

Ad-hoc 線程封閉下的一個特例適用於 volatile 變量。 只要確保 volatile 變量僅從單個線程寫入,就可以安全地對共享 volatile 變量執讀 - 改 - 寫操作。在這種情況下,您將修改限制在單個線程以防止競爭條件,並且 volatile 變量的可見性保證確保其他線程看到最新值。

1.2 棧封閉

棧封閉將變量或對象封閉在線程的棧中。這比 Ad-hoc 線程封閉強得多,因為它通過定義堆棧本身中的變量狀態來進一步限制對象的范圍。例如,請考慮以下代碼:

private long numberOfPeopleNamedJohn(List<Person> people) { List<Person> localPeople = new ArrayList<>(); localPeople.addAll(people); return localPeople.stream().filter(person -> person.getFirstName().equals("John")).count(); } 

在上面的代碼中,我們傳遞一個 person 對象的 list 但不直接使用它。 相反,我們創建自己的 list,該 list 是當前正在執行的線程的本地 list,並將變量 people中的所有 person 添加到 localPeople。由於我們僅在 numberOfPeopleNamedJohn方法中定義列表,這使得變量localPeople 受到堆棧隔離保護,因為它只存在於一個線程的堆棧上,因此任何其他線程都無法訪問它。這使得 localPeople 線程安全。 唯一需要注意的是,不應該讓 localPeople 的作用於超過這個方法的范圍,以保證堆棧的隔離控制。在定義這個變量時,應該記錄或注釋為什么要定義這個變量,通常,只有在當前開發人員的腦海中才不讓它超出方法的作用域,但是在將來,另一個開發人員可能會不知道為何如此設計而陷入困境。

1.3 ThreadLocal

ThreadLocal允許我們將每個線程 ID 與相應對象的值相關聯。 它允許我們為不同的線程存儲不同的對象,並維護哪個對象對應於哪個線程。它有 set 和 get 方法,這些方法為使用它的每個線程維護一個單獨的 value 副本。get() 方法總是返回從當前正在執行的線程傳遞給 set()的最新值。 我們來看一個例子:

public class ThreadConfinementUsingThreadLocal { public static void main(String[] args) { ThreadLocal<String> stringHolder = new ThreadLocal<>(); Runnable runnable1 = () -> { stringHolder.set("Thread in runnable1"); try { Thread.sleep(5000); System.out.println(stringHolder.get()); } catch (InterruptedException e) { e.printStackTrace(); } }; Runnable runnable2 = () -> { stringHolder.set("Thread in runnable2"); try { Thread.sleep(2000); stringHolder.set("string in runnable2 changed"); Thread.sleep(2000); System.out.println(stringHolder.get()); } catch (InterruptedException e) { e.printStackTrace(); } }; Runnable runnable3 = () -> { stringHolder.set("Thread in runnable3"); try { Thread.sleep(5000); System.out.println(stringHolder.get()); } catch (InterruptedException e) { e.printStackTrace(); } }; Thread thread1 = new Thread(runnable1); Thread thread2 = new Thread(runnable2); Thread thread3 = new Thread(runnable3); thread1.start(); thread2.start(); thread3.start(); } } 

在上面的例子中,我們使用相同的 ThreadLocal 對象 stringHolder 執行了三個線程。正如你在這里看到的,我們首先在 stringHolder 對象的每個線程中設置一個字符串,使其包含三個字符串。然后,經過一些暫停后,我們只更改了第二個線程的值。 以下是該程序的輸出:

string in runnable2 changed Thread in runnable1 Thread in runnable3 

正如您在上面的輸出中所看到的,線程2的字符串已更改,但線程1和線程3的字符串未受影響。如果我們在從 ThreadLocal 獲取特定線程的值之前沒有設置任何值,那么它返回null。 線程終止后,“ThreadLocal” 中特定於線程的對象就可以進行垃圾回收了。

這就是線程封閉。 我希望這個博客對你有所幫助,一塊學習新的東西。 謝謝!

原文:https://dzone.com/articles/java-concurrency-thread-confinement

作者:Akshansh Jain

譯者:KeepGoingPawn

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM