Java語言從設計之初就把多線程做爲語言的核心, 至少從如下幾點能夠看出:html
1. Object對象的wait和notify機制。 2. Thread類在lang包中。 3. synchronized volatile關鍵字。
雖然多線程是Java語言自己的特性,可是線程並非Java語言獨有的東西,而是操做系統的特性。Java在語言層面進行了封裝,使其使用更簡單。java
多線程存在的價值在哪裏呢? 內存讀寫,磁盤IO, 網絡傳輸的速率遠遠低於CPU處理數據的速度。因此在大部分場景下,CPU是閒置的。有了多線程,就能夠更多地壓榨CPU。因此,多線程在Web服務器,Lucene這類IO密集型的應用中大行其道。緩存
多線程,知識點龐雜,但經常使用的核心知識點只有兩個: 資源同步 和 線程池。安全
學習多線程,私覺得這樣的前後順序比較好。服務器
step1: 學習多線程的建立和運行, Thread類和Runnable接口, 一般自定義類是實現Runnable接口,並不是Thread類。網絡
public class ThreadDemo { static class Worker impelements Runnable{ private String name; public Worker(String name){ this.name = name; } public void run(){ System.out.println(this.name+": "+Thread.currentThread().getName()); } } public static void main(String[] args) { Thread a1 = new Thread(new Worker("nameA")); Thread a2 = new Thread(new Worker("nameB")); a1.start(); a2.start(); } }
step2: 學習synchronized/volatile關鍵字數據結構
因爲內存讀寫遠比CPU執行的速度慢,出於提高性能的考慮,計算機設計者在CPU和內存之間架設了緩存,就是一般咱們看到的L1, L2, L3。1個CPU週期須要0.3ns,L1緩存只須要0.9ns, 內存訪問須要120ns。 差距達百倍, 可見緩存對性能的提高。若是咱們在一個線程裏讀寫一個變量,將初始值加到緩存後, CPU只需和緩存之間交互就能夠了。多線程
因爲線程之間並不共享緩存,因此多個線程讀寫同一個變量時,就有可能出現不一致的狀況。多個線程出現了信息的不對稱, 如何解決這個問題呢? 就像銀行辦理業務同樣,每一個人辦理業務前先取個號。這個號就相似Java裏面的鎖機制。 synchronized就是Java的內置鎖。 volatile能夠看做synchronized的簡化版, 由於它只能適用於某些特定的場景:ide
1. 修改變量不依賴其當前的值 2. 變量不跟其餘變量一塊兒對外做爲總體
關於volatile的學習,我的以爲IBM的Java theory and practice Managing volatility
是極好的學習資料。工具
step3: 學習ReentrantLock。瞭解了鎖的使用場景後,能夠先學習JUC實現的鎖,最簡單的就是ReentrantLock。ReentrantLock是互斥鎖,可做爲synchronized的代替品。
import java.util.concurrent.locks.ReentrantLock; public class ThreadDemo { static class Counter implements Runnable{ private int count; private ReentrantLock lock = new ReentrantLock(); public void run(){ try{ lock.lock(); count +=1; }finally { lock.unlock(); } } public int getCount() { return count; } } public static void main(String[] args) throws InterruptedException { Counter a1 = new Counter(); for(int i=0;i<1000;i++){ new Thread(a1).start(); } Thread.sleep(1000); System.out.println(a1.getCount()); } }
使用了ReentrantLock後,最好是能理解鎖的實現原理,即大名頂頂的AQS。可是學習AQS以前,須要一些預備知識。
1. volatile關鍵字的使用場景及限制 2. Unsafe.compareAndSwap的使用 3. Unsafe.park/unpark的使用 4. 數據結構鏈表的原理
整個JUC的鎖,線程安全的隊列都是基於AQS構建的。
step4: 學習AQS的原理。AQS基於鏈表構建的隊列來存儲爭用鎖的線程。當線程沒有獲取到鎖,就會使用Unsafe.park將線程掛起;其餘的線程釋放鎖時,就會調用Unsafe.unpark將掛起的線程恢復。
step5: 瞭解了AQS的原理後,再學習ReentrantReadWriteLock, Semaphore, Phase, CountDownLatch, CyclicBarrier 這些同步工具,就沒那麼難理解了。
step6: 學習BlockingQueue及其子類。 各類阻塞隊列,使用頻度比較高的是ArrayBlockingQueue, LinkedBlockingQueue.
step7: 學習線程池,主要是ThreadPoolExecutor類, ThreadPoolExecutor的核心是Worker類,每一個Worker對應着一個線程。須要注意的是使用ThreadPoolExecutor時,阻塞隊列必定要要指定大小。否則線程池的RejectPolicy就不起做用了。 具體的細節須要更深刻的分析。
關於多線程的知識點, 一篇筆記是遠遠不夠的,本文僅僅梳理學習的脈絡。
參考文檔:
https://www.ibm.com/developerworks/library/j-jtp06197/index.html