最近又學到了不少新知識,感謝優銳課老師細緻地講解,這篇博客記錄下本身所學所想,也和你們分享、瞭解有關Java中的併發問題和線程限制的更多信息。安全
在此文中,咱們將探討線程限制,它的含義以及如何實現。所以,讓咱們直接研究它。併發
大多數併發問題僅在咱們但願在線程之間共享可變變量或可變狀態時纔會發生。若是在多個線程之間共享了可變狀態,則全部線程都將可以讀取和修改狀態的值,從而致使錯誤或意外的行爲。避免此問題的一種方法是根本不共享線程之間的數據。該技術被稱爲線程限制,是在咱們的應用程序中實現線程安全的最簡單方法之一。學習
Java語言自己沒有任何執行線程限制的方法。線程限制是經過設計程序的方式實現的,該程序不容許狀態被多個線程使用,所以由實現來強制執行。線程限制有幾種類型,以下所述。spa
臨時線程限制描述了一種線程限制方式,由開發人員或負責該程序的一組開發人員共同負責,以確保對象的使用僅限於單個線程。這種方法很是脆弱,在大多數狀況下應避免使用。線程
Ad-hoc線程限制下的一種特殊狀況適用於volatile變量。只要確保只從單個線程寫入volatile變量,就能夠對共享的volatile變量執行讀-修改-寫操做。在這種狀況下,你將修改限制在單個線程中,以防止出現競爭狀況,而且易失變量的可見性保證可確保其餘線程看到最新的值。設計
堆棧限制是將變量或對象限制在線程的堆棧中。這比臨時線程約束要強得多,由於經過定義堆棧自己中變量的狀態,它甚至進一步限制了對象的範圍。例如,考慮如下代碼:code
1 private long numberOfPeopleNamedJohn(List<Person> people) { 2 List<Person> localPeople = new ArrayList<>(); 3 localPeople.addAll(people); 4 return localPeople.stream().filter(person -> person.getFirstName().equals("John")).count(); 5 }
在上面的代碼中,咱們傳遞了一我的員列表,但沒有直接使用它。 相反,咱們建立本身的列表,該列表位於當前正在執行的線程的本地,並將全部人員添加到localPeople
中。因爲咱們僅在numberOfPeopleNamedJohn
方法中定義列表,所以這使變量localPeople
堆棧受到限制,由於它存在於一個線程的堆棧中,所以沒法被其餘任何線程訪問。這使localPeople
線程安全。咱們惟一須要注意的是,咱們不該該容許localPeople
逃脫此方法的範圍,以使其保持在堆棧限制內。定義此變量時,也應記錄或註釋該變量,一般來講,只有當前的開發人員才能避免使用此變量,未來,另外一位開發人員可能會搞砸了。對象
ThreadLocal
容許你將每一個線程的值與值持有對象相關聯。它容許你爲不一樣的線程存儲不一樣的對象,並維護哪一個對象對應於哪一個線程。它具備set和get訪問器方法,該方法爲使用它的每一個線程維護該值的單獨副本。get()
方法始終返回從當前正在執行的線程傳遞給set()
的最新值。讓咱們看一個例子:blog
1 public class ThreadConfinementUsingThreadLocal { 2 public static void main(String[] args) { 3 ThreadLocal<String> stringHolder = new ThreadLocal<>(); 4 Runnable runnable1 = () -> { 5 stringHolder.set("Thread in runnable1"); 6 try { 7 Thread.sleep(5000); 8 System.out.println(stringHolder.get()); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 }; 13 Runnable runnable2 = () -> { 14 stringHolder.set("Thread in runnable2"); 15 try { 16 Thread.sleep(2000); 17 stringHolder.set("string in runnable2 changed"); 18 Thread.sleep(2000); 19 System.out.println(stringHolder.get()); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 } 23 }; 24 Runnable runnable3 = () -> { 25 stringHolder.set("Thread in runnable3"); 26 try { 27 Thread.sleep(5000); 28 System.out.println(stringHolder.get()); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } 32 }; 33 Thread thread1 = new Thread(runnable1); 34 Thread thread2 = new Thread(runnable2); 35 Thread thread3 = new Thread(runnable3); 36 thread1.start(); 37 thread2.start(); 38 thread3.start(); 39 } 40 }
在上面的示例中,咱們使用相同的ThreadLocal
對象stringHolder
執行了三個線程。如你在這裏看到的,首先,咱們在stringHolder
對象的每一個線程中設置一個字符串,使其包含三個字符串。而後,通過一些暫停後,咱們僅從第二個線程更改了該值。下面是程序的輸出:開發
1 string in runnable2 changed 2 Thread in runnable1 3 Thread in runnable3
如你在上面的輸出中看到的,線程2的字符串已更改,但線程1和線程3的字符串未受影響。若是在從ThreadLocal
獲取特定線程上的值以前未設置任何值,則它將返回null
。終止線程後,ThreadLocal
中特定於線程的對象將準備進行垃圾回收。
這就是關於線程限制的所有內容。我但願這篇博客能對你有所幫助,而且你必須學習新的知識。
謝謝,祝你愉快!