出自[Java中的多線程你只要看這一篇就夠了](http://www.jianshu.com/p/40d4c7aebd66)
[TOC=1,2]
## 引
如果對什么是線程、什么是進程仍存有疑惑,請先Google之,因為這兩個概念不在本文的范圍之內。
用多線程只有一個目的,那就是更好的利用cpu的資源,因為所有的多線程代碼都可以用單線程來實現。說這個話其實只有一半對,因為反應“多角色”的程序代碼,最起碼每個角色要給他一個線程吧,否則連實際場景都無法模擬,當然也沒法說能用單線程來實現:比如最常見的“生產者,消費者模型”。
很多人都對其中的一些概念不夠明確,如同步、并發等等,讓我們先建立一個數據字典,以免產生誤會。
* 多線程:指的是這個程序(一個進程)運行時產生了不止一個線程
* 并行與并發:
* 并行:多個cpu實例或者多臺機器同時執行一段處理邏輯,是真正的同時。
* 并發:通過cpu調度算法,讓用戶看上去同時執行,實際上從cpu操作層面不是真正的同時。并發往往在場景中有公用的資源,那么針對這個公用的資源往往產生瓶頸,我們會用TPS或者QPS來反應這個系統的處理能力。

并發與并行
* 線程安全:經常用來描繪一段代碼。指在并發的情況之下,該代碼經過多線程使用,線程的調度順序不影響任何結果。這個時候使用多線程,我們只需要關注系統的內存,cpu是不是夠用即可。反過來,線程不安全就意味著線程的調度順序會影響最終結果,如不加事務的轉賬代碼:
~~~
void transferMoney(User from, User to, float amount){
to.setMoney(to.getBalance() + amount);
from.setMoney(from.getBalance() - amount);
}
~~~
* 同步:Java中的同步指的是通過人為的控制和調度,保證共享資源的多線程訪問成為線程安全,來保證結果的準確。如上面的代碼簡單加入`@synchronized`關鍵字。在保證結果準確的同時,提高性能,才是優秀的程序。線程安全的優先級高于性能。
好了,讓我們開始吧。我準備分成幾部分來總結涉及到多線程的內容:
1. 扎好馬步:線程的狀態
2. 內功心法:每個對象都有的方法(機制)
3. 太祖長拳:基本線程類
4. 九陰真經:高級多線程控制類
## 扎好馬步:線程的狀態
先來兩張圖:

線程狀態

線程狀態轉換
各種狀態一目了然,值得一提的是"blocked"這個狀態:
線程在Running的過程中可能會遇到阻塞(Blocked)情況
1. 調用join()和sleep()方法,sleep()時間結束或被打斷,join()中斷,IO完成都會回到Runnable狀態,等待JVM的調度。
2. 調用wait(),使該線程處于等待池(wait blocked pool),直到notify()/notifyAll(),線程被喚醒被放到鎖定池(lock blocked pool ),釋放同步鎖使線程回到可運行狀態(Runnable)
3. 對Running狀態的線程加同步鎖(Synchronized)使其進入(lock blocked pool ),同步鎖被釋放進入可運行狀態(Runnable)。
此外,在runnable狀態的線程是處于被調度的線程,此時的調度順序是不一定的。Thread類中的yield方法可以讓一個running狀態的線程轉入runnable。
## 內功心法:每個對象都有的方法(機制)
synchronized, wait, notify 是任何對象都具有的同步工具。讓我們先來了解他們

monitor
他們是應用于同步問題的人工線程調度工具。講其本質,首先就要明確monitor的概念,Java中的每個對象都有一個監視器,來監測并發代碼的重入。在非多線程編碼時該監視器不發揮作用,反之如果在synchronized 范圍內,監視器發揮作用。
wait/notify必須存在于synchronized塊中。并且,這三個關鍵字針對的是同一個監視器(某對象的監視器)。這意味著wait之后,其他線程可以進入同步塊執行。
當某代碼并不持有監視器的使用權時(如圖中5的狀態,即脫離同步塊)去wait或notify,會拋出java.lang.IllegalMonitorStateException。也包括在synchronized塊中去調用另一個對象的wait/notify,因為不同對象的監視器不同,同樣會拋出此異常。
再講用法:
* synchronized單獨使用:
* 代碼塊:如下,在多線程環境下,synchronized塊中的方法獲取了lock實例的monitor,如果實例相同,那么只有一個線程能執行該塊內容
~~~
public class Thread1 implements Runnable {
Object lock;
public void run() {
synchronized(lock){
..do something
}
}
}
~~~
* 直接用于方法: 相當于上面代碼中用lock來鎖定的效果,實際獲取的是Thread1類的monitor。更進一步,如果修飾的是static方法,則鎖定該類所有實例。
~~~
public class Thread1 implements Runnable {
public synchronized void run() {
..do something
}
}
~~~
* synchronized, wait, notify結合:典型場景生產者消費者問題
~~~
/**
* 生產者生產出來的產品交給店員
*/
public synchronized void produce()
{
if(this.product >= MAX_PRODUCT)
{
try
{
wait();
System.out.println("產品已滿,請稍候再生產");
}
catch(InterruptedException e)
{
e.printStackTrace();
}
return;
}
this.product++;
System.out.println("生產者生產第" + this.product + "個產品.");
notifyAll(); //通知等待區的消費者可以取出產品了
}
/**
* 消費者從店員取產品
*/
public synchronized void consume()
{
if(this.product <= MIN_PRODUCT)
{
try
{
wait();
System.out.println("缺貨,稍候再取");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return;
}
System.out.println("消費者取走了第" + this.product + "個產品.");
this.product--;
notifyAll(); //通知等待去的生產者可以生產產品了
}
~~~
##### volatile
多線程的內存模型:main memory(主存)、working memory(線程棧),在處理數據時,線程會把值從主存load到本地棧,完成操作后再save回去(volatile關鍵詞的作用:每次針對該變量的操作都激發一次load and save)。

volatile
針對多線程使用的變量如果不是volatile或者final修飾的,很有可能產生不可預知的結果(另一個線程修改了這個值,但是之后在某線程看到的是修改之前的值)。其實道理上講同一實例的同一屬性本身只有一個副本。但是多線程是會緩存值的,本質上,volatile就是不去緩存,直接取值。在線程安全的情況下加volatile會犧牲性能。
## 太祖長拳:基本線程類
基本線程類指的是Thread類,Runnable接口,Callable接口
Thread 類實現了Runnable接口,啟動一個線程的方法:
~~~
MyThread my = new MyThread();
my.start();
~~~
**Thread類相關方法:**
~~~
//當前線程可轉讓cpu控制權,讓別的就緒狀態線程運行(切換)
public static Thread.yield()
//暫停一段時間
public static Thread.sleep()
//在一個線程中調用other.join(),將等待other執行完后才繼續本線程。
public join()
//后兩個函數皆可以被打斷
public interrupte()
~~~
**關于中斷**:它并不像stop方法那樣會中斷一個正在運行的線程。線程會不時地檢測中斷標識位,以判斷線程是否應該被中斷(中斷標識值是否為true)。終端只會影響到wait狀態、sleep狀態和join狀態。被打斷的線程會拋出InterruptedException。
Thread.interrupted()檢查當前線程是否發生中斷,返回boolean
synchronized在獲鎖的過程中是不能被中斷的。
中斷是一個狀態!interrupt()方法只是將這個狀態置為true而已。所以說正常運行的程序不去檢測狀態,就不會終止,而wait等阻塞方法會去檢查并拋出異常。如果在正常運行的程序中添加while(!Thread.interrupted()) ,則同樣可以在中斷后離開代碼體
**Thread類最佳實踐**:
寫的時候最好要設置線程名稱 Thread.name,并設置線程組 ThreadGroup,目的是方便管理。在出現問題的時候,打印線程棧 (jstack -pid) 一眼就可以看出是哪個線程出的問題,這個線程是干什么的。
**如何獲取線程中的異常**

不能用try,catch來獲取線程中的異常
### Runnable
與Thread類似
### Callable
future模式:并發模式的一種,可以有兩種形式,即無阻塞和阻塞,分別是isDone和get。其中Future對象用來存放該線程的返回值以及狀態
~~~
ExecutorService e = Executors.newFixedThreadPool(3);
//submit方法有多重參數版本,及支持callable也能夠支持runnable接口類型.
Future future = e.submit(new myCallable());
future.isDone() //return true,false 無阻塞
future.get() // return 返回值,阻塞直到該線程運行結束
~~~
## 九陰真經:高級多線程控制類
以上都屬于內功心法,接下來是實際項目中常用到的工具了,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是一個特殊的容器。
### 2.原子類(AtomicInteger、AtomicBoolean……)
如果使用atomic wrapper class如atomicInteger,或者使用自己保證原子的操作,則等同于synchronized
~~~
//返回值為boolean
AtomicInteger.compareAndSet(int expect,int update)
~~~
該方法可用于實現樂觀鎖,考慮文中最初提到的如下場景:a給b付款10元,a扣了10元,b要加10元。此時c給b2元,但是b的加十元代碼約為:
~~~
if(b.value.compareAndSet(old, value)){
return ;
}else{
//try again
// if that fails, rollback and log
}
~~~
**AtomicReference**
對于AtomicReference 來講,也許對象會出現,屬性丟失的情況,即oldObject == current,但是oldObject.getPropertyA != current.getPropertyA。
這時候,AtomicStampedReference就派上用場了。這也是一個很常用的思路,即加上版本號
### 3.Lock類
lock: 在java.util.concurrent包內。共有三個實現:
* ReentrantLock
* ReentrantReadWriteLock.ReadLock
* ReentrantReadWriteLock.WriteLock
主要目的是和synchronized一樣, 兩者都是為了解決同步問題,處理資源爭端而產生的技術。功能類似但有一些區別。
區別如下:
1. lock更靈活,可以自由定義多把鎖的枷鎖解鎖順序(synchronized要按照先加的后解順序)
2. 提供多種加鎖方案,lock 阻塞式, trylock 無阻塞式, lockInterruptily 可打斷式, 還有trylock的帶超時時間版本。
3. 本質上和監視器鎖(即synchronized是一樣的)
4. 能力越大,責任越大,必須控制好加鎖和解鎖,否則會導致災難。
5. 和Condition類的結合。
6. 性能更高,對比如下圖:

synchronized和Lock性能對比
**ReentrantLock**
可重入的意義在于持有鎖的線程可以繼續持有,并且要釋放對等的次數后才真正釋放該鎖。
使用方法是:
1.先new一個實例
~~~
static ReentrantLock r=new ReentrantLock();
~~~
2.加鎖
~~~
r.lock()或r.lockInterruptibly();
~~~
此處也是個不同,后者可被打斷。當a線程lock后,b線程阻塞,此時如果是lockInterruptibly,那么在調用b.interrupt()之后,b線程退出阻塞,并放棄對資源的爭搶,進入catch塊。(如果使用后者,必須throw interruptable exception 或catch)
3.釋放鎖
~~~
r.unlock()
~~~
必須做!何為必須做呢,要放在finally里面。以防止異常跳出了正常流程,導致災難。這里補充一個小知識點,finally是可以信任的:經過測試,哪怕是發生了OutofMemoryError,finally塊中的語句執行也能夠得到保證。
**ReentrantReadWriteLock**
可重入讀寫鎖(讀寫鎖的一個實現)
~~~
ReentrantReadWriteLock lock = new ReentrantReadWriteLock()
ReadLock r = lock.readLock();
WriteLock w = lock.writeLock();
~~~
兩者都有lock,unlock方法。寫寫,寫讀互斥;讀讀不互斥。可以實現并發讀的高效線程安全代碼
### 4.容器類
這里就討論比較常用的兩個:
* BlockingQueue
* ConcurrentHashMap
**BlockingQueue**
阻塞隊列。該類是java.util.concurrent包下的重要類,通過對Queue的學習可以得知,這個queue是單向隊列,可以在隊列頭添加元素和在隊尾刪除或取出元素。類似于一個管 道,特別適用于先進先出策略的一些應用場景。普通的queue接口主要實現有PriorityQueue(優先隊列),有興趣可以研究
BlockingQueue在隊列的基礎上添加了多線程協作的功能:

BlockingQueue
除了傳統的queue功能(表格左邊的兩列)之外,還提供了阻塞接口put和take,帶超時功能的阻塞接口offer和poll。put會在隊列滿的時候阻塞,直到有空間時被喚醒;take在隊 列空的時候阻塞,直到有東西拿的時候才被喚醒。用于生產者-消費者模型尤其好用,堪稱神器。
常見的阻塞隊列有:
* ArrayListBlockingQueue
* LinkedListBlockingQueue
* DelayQueue
* SynchronousQueue
**ConcurrentHashMap**
高效的線程安全哈希map。請對比hashTable , concurrentHashMap, HashMap
### 5.管理類
管理類的概念比較泛,用于管理線程,本身不是多線程的,但提供了一些機制來利用上述的工具做一些封裝。
了解到的值得一提的管理類:ThreadPoolExecutor和 JMX框架下的系統級管理類 ThreadMXBean
**ThreadPoolExecutor**
如果不了解這個類,應該了解前面提到的ExecutorService,開一個自己的線程池非常方便:
~~~
ExecutorService e = Executors.newCachedThreadPool();
ExecutorService e = Executors.newSingleThreadExecutor();
ExecutorService e = Executors.newFixedThreadPool(3);
// 第一種是可變大小線程池,按照任務數來分配線程,
// 第二種是單線程池,相當于FixedThreadPool(1)
// 第三種是固定大小線程池。
// 然后運行
e.execute(new MyRunnableImpl());
~~~
該類內部是通過ThreadPoolExecutor實現的,掌握該類有助于理解線程池的管理,本質上,他們都是ThreadPoolExecutor類的各種實現版本。請參見javadoc:

ThreadPoolExecutor參數解釋
翻譯一下:
**corePoolSize**:池內線程初始值與最小值,就算是空閑狀態,也會保持該數量線程。
**maximumPoolSize**:線程最大值,線程的增長始終不會超過該值。
**keepAliveTime**:當池內線程數高于corePoolSize時,經過多少時間多余的空閑線程才會被回收。回收前處于wait狀態
**unit**:
時間單位,可以使用TimeUnit的實例,如TimeUnit.MILLISECONDS
**workQueue**:待入任務(Runnable)的等待場所,該參數主要影響調度策略,如公平與否,是否產生餓死(starving)
**threadFactory**:線程工廠類,有默認實現,如果有自定義的需要則需要自己實現ThreadFactory接口并作為參數傳入。
> 請注意:該類十分常用,作者80%的多線程問題靠他。
- JVM
- 深入理解Java內存模型
- 深入理解Java內存模型(一)——基礎
- 深入理解Java內存模型(二)——重排序
- 深入理解Java內存模型(三)——順序一致性
- 深入理解Java內存模型(四)——volatile
- 深入理解Java內存模型(五)——鎖
- 深入理解Java內存模型(六)——final
- 深入理解Java內存模型(七)——總結
- Java內存模型
- Java內存模型2
- 堆內內存還是堆外內存?
- JVM內存配置詳解
- Java內存分配全面淺析
- 深入Java核心 Java內存分配原理精講
- jvm常量池
- JVM調優總結
- JVM調優總結(一)-- 一些概念
- JVM調優總結(二)-一些概念
- VM調優總結(三)-基本垃圾回收算法
- JVM調優總結(四)-垃圾回收面臨的問題
- JVM調優總結(五)-分代垃圾回收詳述1
- JVM調優總結(六)-分代垃圾回收詳述2
- JVM調優總結(七)-典型配置舉例1
- JVM調優總結(八)-典型配置舉例2
- JVM調優總結(九)-新一代的垃圾回收算法
- JVM調優總結(十)-調優方法
- 基礎
- Java 征途:行者的地圖
- Java程序員應該知道的10個面向對象理論
- Java泛型總結
- 序列化與反序列化
- 通過反編譯深入理解Java String及intern
- android 加固防止反編譯-重新打包
- volatile
- 正確使用 Volatile 變量
- 異常
- 深入理解java異常處理機制
- Java異常處理的10個最佳實踐
- Java異常處理手冊和最佳實踐
- Java提高篇——對象克隆(復制)
- Java中如何克隆集合——ArrayList和HashSet深拷貝
- Java中hashCode的作用
- Java提高篇之hashCode
- 常見正則表達式
- 類
- 理解java類加載器以及ClassLoader類
- 深入探討 Java 類加載器
- 類加載器的工作原理
- java反射
- 集合
- HashMap的工作原理
- ConcurrentHashMap之實現細節
- java.util.concurrent 之ConcurrentHashMap 源碼分析
- HashMap的實現原理和底層數據結構
- 線程
- 關于Java并發編程的總結和思考
- 40個Java多線程問題總結
- Java中的多線程你只要看這一篇就夠了
- Java多線程干貨系列(1):Java多線程基礎
- Java非阻塞算法簡介
- Java并發的四種風味:Thread、Executor、ForkJoin和Actor
- Java中不同的并發實現的性能比較
- JAVA CAS原理深度分析
- 多個線程之間共享數據的方式
- Java并發編程
- Java并發編程(1):可重入內置鎖
- Java并發編程(2):線程中斷(含代碼)
- Java并發編程(3):線程掛起、恢復與終止的正確方法(含代碼)
- Java并發編程(4):守護線程與線程阻塞的四種情況
- Java并發編程(5):volatile變量修飾符—意料之外的問題(含代碼)
- Java并發編程(6):Runnable和Thread實現多線程的區別(含代碼)
- Java并發編程(7):使用synchronized獲取互斥鎖的幾點說明
- Java并發編程(8):多線程環境中安全使用集合API(含代碼)
- Java并發編程(9):死鎖(含代碼)
- Java并發編程(10):使用wait/notify/notifyAll實現線程間通信的幾點重要說明
- java并發編程-II
- Java多線程基礎:進程和線程之由來
- Java并發編程:如何創建線程?
- Java并發編程:Thread類的使用
- Java并發編程:synchronized
- Java并發編程:Lock
- Java并發編程:volatile關鍵字解析
- Java并發編程:深入剖析ThreadLocal
- Java并發編程:CountDownLatch、CyclicBarrier和Semaphore
- Java并發編程:線程間協作的兩種方式:wait、notify、notifyAll和Condition
- Synchronized與Lock
- JVM底層又是如何實現synchronized的
- Java synchronized詳解
- synchronized 與 Lock 的那點事
- 深入研究 Java Synchronize 和 Lock 的區別與用法
- JAVA編程中的鎖機制詳解
- Java中的鎖
- TreadLocal
- 深入JDK源碼之ThreadLocal類
- 聊一聊ThreadLocal
- ThreadLocal
- ThreadLocal的內存泄露
- 多線程設計模式
- Java多線程編程中Future模式的詳解
- 原子操作(CAS)
- [譯]Java中Wait、Sleep和Yield方法的區別
- 線程池
- 如何合理地估算線程池大小?
- JAVA線程池中隊列與池大小的關系
- Java四種線程池的使用
- 深入理解Java之線程池
- java并發編程III
- Java 8并發工具包漫游指南
- 聊聊并發
- 聊聊并發(一)——深入分析Volatile的實現原理
- 聊聊并發(二)——Java SE1.6中的Synchronized
- 文件
- 網絡
- index
- 內存文章索引
- 基礎文章索引
- 線程文章索引
- 網絡文章索引
- IOC
- 設計模式文章索引
- 面試
- Java常量池詳解之一道比較蛋疼的面試題
- 近5年133個Java面試問題列表
- Java工程師成神之路
- Java字符串問題Top10
- 設計模式
- Java:單例模式的七種寫法
- Java 利用枚舉實現單例模式
- 常用jar
- HttpClient和HtmlUnit的比較總結
- IO
- NIO
- NIO入門
- 注解
- Java Annotation認知(包括框架圖、詳細介紹、示例說明)