java 多線程
前言
線程安全出現的問題例子:
比如一個 ArrayList 類,在添加一個元素的時候,它可能會有兩步來完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
在單線程運行的情況下,如果 Size = 0,添加一個元素后,此元素在位置 0,而且 Size=1;
而如果是在多線程情況下,比如有兩個線程,線程 A 先將元素1存放在位置 0。但是此時 CPU 調度線程A暫停,線程 B 得到運行的機會。線程B向此 ArrayList 添加元素2,因為此時 Size 仍然等于 0 (注意,我們假設的是添加一個元素是要兩個步驟,而線程A僅僅完成了步驟1),所以線程B也將元素存放在位置0。然后線程A和線程B都繼續運行,都增加 Size 的值,結果Size等于2。
那好,我們來看看 ArrayList 的情況,期望的元素應該有2個,而實際只有一個元素,造成丟失元素,而且Size 等于 2。這就是“線程不安全”了。
線程安全主要是由于:共享變量及共享變量的修改引起的問題。
相應的解決方法是:
線程中的局部變量不需要同步;線程內操作的,同時線程外需要用到的,需要同步!
講解
如果對什么是線程、什么是進程仍存有疑惑,請先Google之,因為這兩個概念不在本文的范圍之內。
用多線程只有一個目的,那就是更好的利用cpu的資源,因為所有的多線程代碼都可以用單線程來實現。說這個話其實只有一半對,因為反應“多角色”的程序代碼,最起碼每個角色要給他一個線程吧,否則連實際場景都無法模擬,當然也沒法說能用單線程來實現:比如最常見的“生產者,消費者模型”。
很多人都對其中的一些概念不夠明確,如同步、并發等等,讓我們先建立一個數據字典,以免產生誤會。
- 多線程:指的是這個程序(一個進程)運行時產生了不止一個線程
- 并行與并發
- 并行:多個cpu實例或者多臺機器同時執行一段處理邏輯,是真正的同時。
- 并發:通過cpu調度算法,讓用戶看上去同時執行,實際上從cpu操作層面不是真正的同時。并發往往在場景中有公用的資源,那么針對這個公用的資源往往產生瓶頸,我們會用TPS或者QPS來反應這個系統的處理能力。
好了,讓我們開始吧。我準備分成幾部分來總結涉及到多線程的內容:
- 扎好馬步:線程的狀態
- 內功心法:每個對象都有的方法(機制)
- 太祖長拳:基本線程類
- 九陰真經:高級多線程控制類
找好馬步:線程的狀態
如下圖所示:
內功心法:每個對象都有的方法(機制)
synchronized, wait, notify 是任何對象都具有的同步工具。
太祖長拳:基本線程類
基本線程類指的是Thread類,Runnable接口,Callable接口,Thread 類實現了Runnable接口.
九陰真經:高級多線程控制類
以上都屬于內功心法,接下來是實際項目中常用到的工具了,Java1.5提供了一個非常高效實用的多線程包:java.util.concurrent, 提供了大量高級工具,可以幫助開發者編寫高效、易維護、結構清晰的Java多線程程序。
1.ThreadLocal類
用處:保存線程的獨立變量。對一個線程類(繼承自Thread)
當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。常用于用戶登錄控制,如記錄session信息。
實現:每個Thread都持有一個TreadLocalMap類型的變量(該類是一個輕量級的Map,功能與map一樣,區別是桶里放的是entry而不是entry的鏈表。功能還是一個map。)以本身為key,以目標為value。
主要方法是get()和set(T a),set之后在map里維護一個threadLocal -> a,get時將a返回。ThreadLocal是一個特殊的容器。
原子類(AtomicInteger、AtomicBoolean……)
5.管理類(最常用)
管理類的概念比較泛,用于管理線程,本身不是多線程的,但提供了一些機制來利用上述的工具做一些封裝。
了解到的值得一提的管理類:ThreadPoolExecutor和 JMX框架下的系統級管理類 ThreadMXBean
ThreadPoolExecutor
如果不了解這個類,應該了解前面提到的ExecutorService,開一個自己的線程池非常方便: