Java并發(fā)中線程封閉知識點(diǎn)詳解
在這篇文章中,我們將探討線程封閉是什么意思,以及我們?nèi)绾螌?shí)現(xiàn)它。 所以,讓我們直接開始吧。
線程封閉基礎(chǔ)知識點(diǎn)
實(shí)現(xiàn)好的并發(fā)是一件困難的事情,所以很多時候我們都想躲避并發(fā)。避免并發(fā)最簡單的方法就是線程封閉。什么是線程封閉呢?
就是把對象封裝到一個線程里,只有這一個線程能看到此對象。那么這個對象就算不是線程安全的也不會出現(xiàn)任何安全問題。實(shí)現(xiàn)線程封閉有哪些方法呢?
1:ad-hoc線程封閉
這是完全靠實(shí)現(xiàn)者控制的線程封閉,他的線程封閉完全靠實(shí)現(xiàn)者實(shí)現(xiàn)。也是最糟糕的一種線程封閉。所以我們直接把他忽略掉吧。
2:棧封閉
棧封閉是我們編程當(dāng)中遇到的最多的線程封閉。什么是棧封閉呢?簡單的說就是局部變量。多個線程訪問一個方法,此方法中的
局部變量都會被拷貝一分兒到線程棧中。所以局部變量是不被多個線程所共享的,也就不會出現(xiàn)并發(fā)問題。所以能用局部變量就別用全局的變量,全局變量容易引起并發(fā)問題。
3:ThreadLocal封閉
使用ThreadLocal是實(shí)現(xiàn)線程封閉的最好方法,有興趣的朋友可以研究一下ThreadLocal的源碼,其實(shí)ThreadLocal內(nèi)部維護(hù)了一個Map,Map的key是每個線程的名稱,而Map的值就是我們要封閉的對象。每個線程中的對象都對應(yīng)著Map中一個值,也就是ThreadLocal利用Map實(shí)現(xiàn)了對象的線程封閉。這里就不說ThreadLocal的使用方法了,度娘一下便知。
1. 線程封閉
大多數(shù)的并發(fā)問題僅發(fā)生在我們想要在線程之間共享可變變量或可變狀態(tài)時。如果在多個線程之間操作共享變量,則所有線程都將能夠讀取和修改變量的值,從而出現(xiàn)意外或不正確的結(jié)果。一種簡單的避免此問題的方式是不在線程之間共享數(shù)據(jù)。 這種技術(shù)稱為線程封閉,是在我們的應(yīng)用程序中實(shí)現(xiàn)線程安全的最簡單方法之一。
Java 語言本身沒有任何強(qiáng)制執(zhí)行線程封閉的方法。線程封閉是通過不允許多個線程同時使用同一個狀態(tài)的方式的程序設(shè)計來實(shí)現(xiàn)的,因此由程序?qū)崿F(xiàn)強(qiáng)制執(zhí)行。 幾種類型的線程封閉,如下所示:
1.1 Ad-Hoc 線程封閉
Ad-hoc 線程封閉描述了線程封閉的方式,由開發(fā)人員或從事該項(xiàng)目的開發(fā)人員確保僅在單個線程內(nèi)使用此對象。 這種方式方法可用性不高,在大多數(shù)情況下應(yīng)該避免。
Ad-hoc 線程封閉下的一個特例適用于 volatile 變量。 只要確保 volatile 變量僅從單個線程寫入,就可以安全地對共享 volatile 變量執(zhí)讀 - 改 - 寫操作。在這種情況下,您將修改限制在單個線程以防止競爭條件,并且 volatile 變量的可見性保證確保其他線程看到最新值。
1.2 棧封閉
棧封閉將變量或?qū)ο蠓忾]在線程的棧中。這比 Ad-hoc 線程封閉強(qiáng)得多,因?yàn)樗ㄟ^定義堆棧本身中的變量狀態(tài)來進(jìn)一步限制對象的范圍。例如,請考慮以下代碼:
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 但不直接使用它。 相反,我們創(chuàng)建自己的 list,該 list 是當(dāng)前正在執(zhí)行的線程的本地 list,并將變量 people中的所有 person 添加到 localPeople。由于我們僅在 numberOfPeopleNamedJohn方法中定義列表,這使得變量localPeople 受到堆棧隔離保護(hù),因?yàn)樗淮嬖谟谝粋€線程的堆棧上,因此任何其他線程都無法訪問它。這使得 localPeople 線程安全。 唯一需要注意的是,不應(yīng)該讓 localPeople 的作用于超過這個方法的范圍,以保證堆棧的隔離控制。在定義這個變量時,應(yīng)該記錄或注釋為什么要定義這個變量,通常,只有在當(dāng)前開發(fā)人員的腦海中才不讓它超出方法的作用域,但是在將來,另一個開發(fā)人員可能會不知道為何如此設(shè)計而陷入困境。
1.3 ThreadLocal
ThreadLocal允許我們將每個線程 ID 與相應(yīng)對象的值相關(guān)聯(lián)。 它允許我們?yōu)椴煌木€程存儲不同的對象,并維護(hù)哪個對象對應(yīng)于哪個線程。它有 set 和 get 方法,這些方法為使用它的每個線程維護(hù)一個單獨(dú)的 value 副本。get() 方法總是返回從當(dāng)前正在執(zhí)行的線程傳遞給 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 執(zhí)行了三個線程。正如你在這里看到的,我們首先在 stringHolder 對象的每個線程中設(shè)置一個字符串,使其包含三個字符串。然后,經(jīng)過一些暫停后,我們只更改了第二個線程的值。 以下是該程序的輸出:
string in runnable2 changed Thread in runnable1 Thread in runnable3
正如您在上面的輸出中所看到的,線程2的字符串已更改,但線程1和線程3的字符串未受影響。如果我們在從 ThreadLocal 獲取特定線程的值之前沒有設(shè)置任何值,那么它返回null。 線程終止后,“ThreadLocal” 中特定于線程的對象就可以進(jìn)行垃圾回收了。
相關(guān)文章
SpringBoot工程打包后執(zhí)行Java?-Jar就能啟動的步驟原理
這篇文章主要介紹了SpringBoot工程打包后為何執(zhí)行Java?-Jar就能啟動,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-05-05SpringBoot+Vue前后端分離實(shí)現(xiàn)審核功能的示例
在實(shí)際開發(fā)中,審核功能是一個非常常用的功能,本文就來介紹一下使用SpringBoot+Vue前后端分離實(shí)現(xiàn)審核功能的示例,具有一定的參考價值,感興趣的可以了解一下2024-02-02使用Spring Boot搭建Java web項(xiàng)目及開發(fā)過程圖文詳解
這篇文章主要介紹了使用Spring Boot搭建Java web項(xiàng)目及開發(fā)過程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06Springboot?jpa使用sum()函數(shù)返回結(jié)果如何被接收
這篇文章主要介紹了Springboot?jpa使用sum()函數(shù)返回結(jié)果如何接收,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02使用springboot每日推送早安問候語到用戶微信的全過程
近期網(wǎng)上又出現(xiàn)一股給女朋友做微信公眾號推送的潮流,所以這篇文章主要給大家介紹了關(guān)于如何使用springboot每日推送早安問候語到用戶微信的相關(guān)資料,文中通過圖文以及實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11