线程魔术技巧:使用Java线程可以做的5件事
Java線程最鮮為人知的事實和用例是什么?
有些人喜歡爬山,有些人喜歡跳傘。 我,我喜歡Java。 我喜歡它的一件事是,您永不停止學習。 您每天使用的工具通??梢詾槟鷰砣碌拿婷?#xff0c;以及您還沒有機會看到的方法和有趣的用例。 例如線程。 實際線程。 或者更好的是,Thread類本身。 當我們使用高可伸縮性系統時,并發編程永遠不會停止挑戰,但是現在我們將討論一些不同的東西。
在這篇文章中,您將看到線程支持的一些鮮為人知但有用的技術和方法。 無論您是初學者,高級用戶還是專家Java開發人員,都請嘗試查看其中哪些已為您所知,以及哪些是新產品。 關于線程,您還有其他值得一提的地方嗎? 我希望在下面的評論中聽到它。 讓我們開始吧。
初學者
1.線程名稱
應用中的每個線程都有一個名稱,即構造該線程時為其生成的簡單Java字符串。 默認名稱值從“ Thread-0”到“ Thread-1”,“ Thread-2”,依此類推。 現在出現了更有趣的部分–線程公開了兩種可用來設置其名稱的方法:
1.線程構造函數,這是最簡單的一個:
class SuchThread extends Thread {Public void run() {System.out.println ("Hi Mom! " + getName());}}SuchThread wow = new SuchThread("much-name");2.線程名稱設置器:
wow.setName(“Just another thread name”);是的,線程名是可變的。 因此,除了在實例化它們時設置自定義名稱外,我們還可以在運行時更改它。 名稱字段本身設置為簡單的String對象。 這意味著它最多可以包含231-1個字符(Integer.MAX_VALUE)。 我說的綽綽有余。 請注意,該名稱不像唯一ID,因此線程可以共享相同的名稱。 另外,不要嘗試將null作為名稱傳遞,除非您希望引發異常(不過“ null”是可以的,我沒有判斷!)。
使用線程名稱進行調試
因此,既然您已經可以訪問線程名,那么遵循自己的一些命名約定可以在發生問題時使您的生活變得更加輕松。 “ Thread-6”聽起來有些無情,我相信您可以想到一個更好的名字。 在處理用戶請求時,將其與自分配的事務ID結合在一起,將其附加到線程的名稱上,您將大大減少錯誤解決時間。
保留在此處的一個好習慣是確保您在應用程序的每個線程的入口點生成一個UUID,并在請求在節點,進程和線程之間傳遞時保持一致。 讓我們看一下這個示例,某個線程池中的一個工作線程掛起了太長時間。 您運行jstack仔細看一下,然后看到以下內容:
“pool-1-thread-1″ #17 prio=5 os_prio=31 tid=0x00007f9d620c9800 nid=0x6d03 in Object.wait() [0x000000013ebcc000]好的,“ pool-1-thread-1”,為什么這么嚴重? 讓我們更好地了解您,并想出一個更合適的名稱:
Thread.currentThread().setName(Context + TID + Params + current Time, ...);現在,當我們再次運行jstack時,情況看起來好多了:
”Queue Processing Thread, MessageID: AB5CAD, type: AnalyzeGraph, queue: ACTIVE_PROD, Transaction_ID: 5678956, Start Time: 30/12/2014 17:37″ #17 prio=5 os_prio=31 tid=0x00007f9d620c9800 nid=0x6d03 in Object.wait() [0x000000013ebcc000]我們知道線程在阻塞時正在做什么,并且還擁有啟動所有線程的事務ID。 您可以追溯步驟,重現錯誤,隔離并解決問題。 要了解更多有關使用jstack的酷方法的信息,您可以在這里查看這篇文章。
2.線程優先級
優先級是另一個有趣的領域線程。 線程的優先級是介于1(MIN_PRIORITY)到10(MAX_PRIORITY)之間的值,主線程的默認值為5(NORM_PRIORITY)。 每個新線程都獲得其父級的優先級,因此,如果您不手動使用它,則您所有線程的優先級都可能設置為5。這也是Thread類的一個經常被忽略的字段,我們可以訪問和操作它。通過方法getPriority()和setPriority() 。 無法在線程構造函數中進行設置。
誰仍然需要優先考慮?
當然,并非所有線程都是平等創建的,有些線程需要您的CPU立即關注,而其他線程只是后臺任務。 優先級用于向OS線程調度程序發出信號。 在Takipi,我們開發了一個錯誤跟蹤和分析工具,為用戶處理新異常的線程將獲得MAX_PRIORITY,而處理諸如報告新部署等任務的線程將獲得較低的優先級。 可能希望具有更高優先級的線程從與JVM一起使用的線程調度程序中獲得更多時間。 好吧,并非總是如此。
每個Java線程都會在OS級別上打開一個新的本機線程,并且您為每個平臺以不同的方式將您設置的Java優先級轉換為本機優先級。 在Linux上,在運行應用程序時,還必須包括“ -XX:+ UseThreadPriorities”標志,以將其考慮在內。 話雖如此,線程優先級仍然只是您提供的建議。 與本機Linux優先級相比,它們甚至沒有涵蓋整個值范圍(1..99,以及線程范圍的影響范圍在-20..20之間)。 主要要點是保持自己的邏輯以確保優先級在每個線程獲得的CPU時間中得到反映的重要性,但不建議僅依賴優先級。
高級
3.線程本地存儲
這與我們在這里談論的其他生物有些不同。 ThreadLocal是一個從Thread類( java.lang.ThreadLocal )實現的概念,但是為每個線程存儲唯一的數據。 就像上面說的那樣,它為您提供了線程本地存儲,這意味著您可以創建每個線程實例唯一的變量。 與您擁有線程名稱或優先級的方式類似,您可以創建自定義字段,使其看起來像是Thread類的成員。 那不是很酷嗎? 但是,我們不要太激動 ,前面有一些警告。
建議使用以下兩種方法之一創建ThreadLocal:作為靜態變量或單例的一部分,在該變量中不必是靜態的。 請注意,它位于全局范圍內,但對每個能夠訪問它的線程都作用于本地。 這是一個ThreadLocal變量的示例,該變量持有我們自己的數據結構以便于訪問:
public?static class CriticalData {public int transactionId;public int username; }public static final ThreadLocal<CriticalData> globalData =new ThreadLocal<CriticalData>();一旦有了ThreadLocal,就可以使用globalData.set()和globalData.get()對其進行訪問 。
全球? 一定是邪惡的
不必要。 ThreadLocal變量可以保留事務ID。 當您有一個未捕獲的異常使您的代碼冒泡時,這可以派上用場。 一個好的做法是設置一個UncaughtExceptionHandler ,我們也可以通過Thread類獲得它,但必須自己實現。 一旦我們到達那個階段,關于實際上是什么使我們到達那里的提示就不多了。 我們剩下的是Thread對象,當堆??蚣荜P閉時,無法訪問將我們帶到那里的任何變量。 在我們的UncaughtExceptionHandler中,隨著線程的最后呼吸,ThreadLocal幾乎是我們剩下的僅有的事情之一。
我們可以本著以下精神做一些事情:
System.err.println("Transaction ID " + globalData.get().transactionId);就像這樣,我們為錯誤添加了一些有價值的上下文。 使用ThreadLocal的一種更有創意的方法是通過分配指定的內存塊,以供工作線程反復使用作為緩沖區。 當然,這可能會很有用,具體取決于您在內存的哪一側與CPU開銷之間的權衡。 也就是說,要注意的是濫用我們的內存空間。 只要特定的線程處于活動狀態,它就存在于特定線程中,除非將其釋放或線程死亡,否則不會被垃圾回收。 因此,在使用它時最好小心并保持簡單。
4.用戶線程和守護程序線程
回到我們的線程類。 我們應用中的每個線程都會收到“用戶”或“守護程序”狀態。 換句話說,前景或后臺線程。 默認情況下,主線程是用戶線程,每個新線程都獲得創建它的線程的狀態。 因此,如果將線程設置為守護程序,則它創建的所有線程也將被標記為守護程序。 當您的應用程序中剩下的僅處于運行狀態的線程處于守護程序狀態時,該過程將關閉。 要進行測試,檢查和更改線程狀態,我們有布爾值.setDaemon(true)和.isDaemon()方法。
您何時設置Daemon線程?
當線程對線程的結束不是很關鍵時,應將其狀態更改為守護進程,以便進程可以關閉。 它消除了正確關閉線程,立即停止所有操作的麻煩,讓我們Swift結束了它。 另一方面,當有一個線程運行的操作必須正確結束時,否則將發生不良情況,請確保將其設置為用戶線程。 關鍵事務可以是例如數據庫條目或完成不間斷的更新。
專家
5. Java處理器親和力
這部分使我們更接近代碼與金屬相遇的硬件。 處理器關聯允許您將線程或進程綁定到特定的CPU內核。 這意味著無論何時執行該特定線程,它都將專門在一個特定內核上運行。 通常情況下,操作系統線程調度程序將根據其自己的邏輯擔當此角色,可能會考慮我們前面提到的線程優先級。
討價還價的籌碼是CPU緩存。 如果一個線程只在一個特定的內核上運行,則更有可能享受將所有數據準備好在緩存上的樂趣。 當數據已經存在時,無需重新加載它。 您節省的微秒數可以被更好地利用,并且代碼實際上將在該時間運行,從而更好地利用分配的CPU時間。 盡管確實在操作系統級別進行了一些優化,并且硬件體系結構當然也起著重要作用,但是使用親和力可以消除線程切換內核的機會。
由于這里有許多因素在起作用,因此確定處理器親和力將如何影響您的吞吐量的最佳方法是接受測試的習慣。 盡管可能并不總是會好得多,但是您可能會遇到的好處之一就是吞吐量穩定。 親和力策略可以降低到手術水平,具體取決于獲得的收益。 高頻交易行業將是這類事情最重要的地方之一。
測試處理器親和力
Java沒有對處理器相似性的本機支持,但這當然還沒有結束。 在Linux上,我們可以使用tasket命令設置進程親和力。 假設我們正在運行一個Java進程,并且希望將其固定到特定的CPU:
taskset -c 1 “java AboutToBePinned”或者,如果它已經在運行:
taskset -c 1?<PID>現在,要進入線程級別,我們需要插入一些新代碼。 幸運的是,有一個開源庫可以幫助我們做到這一點: Java-Thread-Affinity 。 由OpenHFT的Peter Lawrey撰寫,這可能是最簡單的方法。 讓我們看一個固定線程的簡單例子,更多的信息可以在該庫的GitHub存儲庫中找到:
AffinityLock al = AffinityLock.acquireLock();就是這樣。 GitHub上提供了用于獲取鎖的更高級選項,其中考慮了選擇特定內核的不同策略。
結論
我們已經看到了5種查看線程的方法:線程名稱,線程本地存儲,優先級,守護程序線程和相似性。 希望這對您每天處理的事情有所幫助,并很高興聽到您的評論! 還有哪些其他線程處理方法可以適用?
翻譯自: https://www.javacodegeeks.com/2015/01/thread-magic-tricks-5-things-you-never-knew-you-can-do-with-java-threads.html
總結
以上是生活随笔為你收集整理的线程魔术技巧:使用Java线程可以做的5件事的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: eap和psk_针对WildFly和EA
- 下一篇: 路由查看命令(路由查看 linux)