#1 線程介紹 ##1.1 啓動線程java
1.1.1 新建一個類繼承Thread類,重寫run()方法。安全
常規寫法多線程
class A extends Thread { @Override public void run() { System.out.println("......"); } public static void main(String[] args) { A a = new A(); a.start; // 啓動線程 } }
匿名子類寫法併發
class A { public static void main(String[] args) { Thread thread = new Thread() { @Override public void run() { System.out.println("......"); } }; thread.start(); } }
1.1.2 將邏輯定義在Runnable的run()方法中。ide
常規寫法測試
class A implements Runnable{ @Override public void run() { System.out.println("......"); } public void main(String[] args) { A a = new A(); Thread thread = new Thread(a); thread.start(); } }
匿名內部類this
class A { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("......"); } }); thread.start(); } }
Lambda表達式寫法操作系統
class A { public static void main(String[] args) { Thread thread = new Thread(() -> { System.out.println("......"); }); thread.start(); } }
##1.2 啓動線程方式的比較線程
Java單繼承多實現,因此使用Runnable會更靈活,通常要使用Thread中的方法纔會去繼承Thread。code
##1.3 啓動線程的注意點 不管是繼承重寫run()方法仍是實現Runnable接口中的run()方法,在run方法中只是寫上你但願新開闢的線程爲你作的邏輯。真正啓動線程是須要主線程去調用新開闢的線程的start()方法才能啓用線程去執行run()方法。
tips:啓動JVM後不單隻有一個主線程,還有垃圾回收,和內存管理等線程。
#2線程聲明週期 ##2.1 Daemon 線程 主線程會從main()方法開始,直到main()方法結束後退出JVM。若是主線程中啓動了額外的線程,默認會等待全部線程都執行完run()方法才退出JVM。若是有一個Thread被標記爲Daemon線程,在全部非Daemon線程結束後Daemon線程纔會結束而且退出JVM。 經常使用API:設置Daemon線程setDaemon(),判斷Daemon是否爲Daemon線程isDaemon();
tips:默認從Daemon線程產生的線程是也Daemon線程,因此在生成它的線程結束的時候也一併中止。
##2.2 線程狀態圖線程狀態圖以及經常使用API解釋
2.2.1 線程狀態圖
調用線程的start()方法後,通常有三種狀態Runnable(就緒)、Running(運行)、Blocked(阻塞)。 當線程執行sart()方法後線程進入Runnable(狀態)等待Scheduler(排班器)排入CPU執行,線程纔會執行run()方法進入Running(運行)狀態。 CPU同一個時間點上只會執行一個線程,但CPU會不斷切換線程工做,這樣使CPU實現了並行操做,這樣使得線程看起來是同時執行的。
tips: 串行操做(按照執行順序一個個來),並行操做(一會執行這,一會執行那),併發(須要多核CPU每一個CPU處理分別處理一個操做)。
線程能夠設置優先權經過setPriority()方法,默認5,範圍1-10,數值越大,Scheduler(排版器)優先排入CPU執行。
Blocked(阻塞)通常有如下集中狀況:
1 Thread.sleep() 線程休眠狀態,此時線程是不會釋放鎖,當線程醒來(天然醒或者interrupt()喚醒)仍是帶着鎖的,而後等到Scheduler排到他而後繼續執行。
2 進入synchronized前競爭對象鎖的阻斷
3 調用線程wait()方法,線程釋放對象鎖進入對象的等待集合,當notify()通知他的時候,等待Scheduler排到他執行並再次去競爭鎖。
4 等待輸入/輸出完成,讀寫大文件此時CPU是在阻塞在等待操做系統完成IO操做。
tips:當一個線程進入Blocked狀態,讓一個線程排入到CPU執行(成爲Running狀態),避免CPU空閒,是運用多線程改進效能的方式之一。
2.2.2 經常使用API-join() A線程正在運行,流程中使用join()操做將B線程加入,那麼等到B線程執行完畢後再執行A線程。 join(xxxms)表示當插入的線程至多處理xxxms若是xxxms到了插入線程還沒執行完畢,那麼目前線程可繼續執行本來工做。
2.2.3 中止線程
線程完成run方法後,就會進入Dead,已經調用過的start()方法的線程不能再次調用。 stop()方法,將不理會所設定的釋放、取得鎖流程,線程會直接釋放全部已鎖定的對象,這有可能使對象陷入沒法預期的狀態,除了stop()方法外,Thread的resume()、suspend()、destroy()等方法也已經Deprecated。
中止線程最好自行操做,讓線程跑完應有流程而不是用stop()方法。
class A implements Runnable { private boolean flag = true; public void stop() { this.flag = false; } @Override public void run() { while (flag) { System.out.println("......"); } } }
2.2.4 ThreadGroup
每一個線程都屬於某個ThreadGroup,如在main()方法中產生一個線程那麼就屬於main線程羣組。Thread.currentThread().getThreadGroup().getName();得到羣組名。每一個線程產生的時候都會納入某個線程羣組中,能夠指定行線程羣組,不指定就納入產生該子線程的線程羣組,一旦指定線程羣組就沒法更換。ThreadGroup的interrup、setMaxPriority()對羣組中全部的線程都有做用。
當ThreadGroup中的線程發生異常,執行順序以下:
1 ThreadGroup若是有父ThreadGroup那麼調用父ThreadGroup的uncaughtException()方法。
2 看Thread中是否使用setUncaughtExceptionHandler()方法設定Thread.uncaughtExceptionHandler()實例,調用其uncaughtException()方法。
3 看下異常是否爲ThreadDeath實力,是的話什麼都不處理,不是的話打印堆棧。
能夠重寫ThreadGroup.uncaughtException以及Thread中的uncaughtException()這兩個方法來實現本身所須要的異常控制。
2.2.5 synchronized 與 volatile
每一個對象都會有個內部鎖(Intrinsic Lock)或叫監控鎖(Monitor Lock)。 任何線程要執行synchronized區塊都必須先得到指定對象的鎖。若是A線程取得對象鎖開始執行synchronized區塊,B線程也想執行synchronized區塊,但因沒法拿到對象鎖而進入等待狀態(Blocked),直到A線程釋放鎖(如執行完synchronized區塊,或者A線程被wait()了),B線程纔有機會去取得鎖而執行synchronized區塊。線程若因嘗試執行synchronized區塊而進入Blocked,在取得鎖以後,會先進入Runnable狀態,等待CPU的scheduler排入Running狀態。
synchronized放置的幾種位置
1 放在方法上(粗粒度)
public synchronized void add(Object object) { System.out.println("運行邏輯"); }
2 代碼塊(相對細粒度)
public void add(Object object) { // ... synchronized (this) { System.out.println("運行邏輯"); } // ... }
3 對於不是線程安全的類的方法
ArrayList<Integer> arrayList = new ArrayList<>(); Thread thread = new Thread(() -> { // ... synchronized (arrayList) { System.out.println("運行邏輯"); } // ... });
4 使用Collections中的synchronizedCollection()、synchronizedList()、 synchronizedSet()、synchronizedMap()將傳入的Collection、List、Set、Map、對象打包返回線程安全的對象
將上面的代碼簡化
List<Integer> list = Collections.synchronizedList(new ArrayList<>()); // ... list.add(Integer.valueOf("....")); // ...
使用synchronized時,能夠經過不一樣對象作爲鎖的來源實現更細粒度的控制。
class A<E> { private int data1 = 0; private int data2 = 0; private E e1; private E e2; public void doSome() { System.out.println("其餘邏輯"); synchronized (e1) { data1++; } System.out.println("其餘邏輯"); } public void doOther() { System.out.println("其餘邏輯"); synchronized (e2) { data2++; } System.out.println("其餘邏輯"); } }
此時doSome()和doOther()不會同時被兩個以上的線程執行,而且data1與data2不一樣時出如今兩個方法中,不會引起內存共享問題,而且此時不一樣方法提供不一樣對象作爲鎖的來源,這樣不會致使一個線程在拿到鎖以後執行doSome()中synchronized代碼塊,另外一個線程在doOther()中的synchronized那發生獲取鎖的等待問題了。
Java中的synchronized提供可重入同步(Reentrant Synchronized),線程在獲取某對象鎖定後,在執行的過程當中又要執行synchronized,此時取得鎖對象是一個的話,那麼能夠直接執行。
死鎖:當一個線程獲取A對象鎖以後須要獲取B對象鎖時,假設另外一個線程獲取B對象鎖以後須要獲取A對象鎖,此時第一個線程由於要獲取B對象鎖進入Blocked,第二個線程要獲取A對象鎖也進入Blocked。
2.2.5 使用volatile
synchronized對所標誌區塊具備互斥性與可見性,互斥行:synchronized區塊同一時間只有一個線程,可見性:線程離開synchronized區塊後,另外一個線程所接觸到的就是上一個線程改變後的對象狀態。
在Java中對可見性的要求,可使用volatile達到變量範圍。
正常狀況下線程能夠快取變量值,線程能夠從共享內存中快取變量值放到本身的內存空間,而後將本身內存空間中快取的值輸出出去。這可能致使當線程A快取一個值data以後,這個值data被線程B所改變,但此時A不會再取共享內存空間中拿到變化以後的值data而是直接拿本身內存空間中快取的值olddata輸出出去。
此時能夠在變量上聲明volatile,表示變量不穩定,可能在多線程下存取,這保證了變量的可見性,當有線程變更了變量值,另外一個線程可看到變化。被volatile聲明的變量,不容許線程快取,變量值的存取必定是在共享內存中進行的。
volatiole保證的是單一變數的可見行,線程對變量的存取必定是在共享內存中的,不會在本身的內存空間中快取變量,線程對共享內存中變量的存取,另外一個線程必定看獲得。
2.2.6 等待與通知
wai() notify() notifyAll() 是Object定義的方法,經過這三個方法控制線程釋放對象的鎖,或者通知線程參與鎖的競爭。
線程進入synchronized範圍前,須要先得到對象的鎖。執行synchronozed範圍的程序代碼期間,若調用鎖定對象的wait()方法,線程會釋放對象的鎖,進入此對象的等待集合(WaitSet)而處於Blocked,此時其餘線程能夠競爭對象鎖,拿到鎖以後執行synchronized代碼塊。
tips: 調用wait()方法的時候必須鎖定該對象。
放在等待集合的線程不參與CPU排班,wait()能夠指定時間,時間到以後線程進入排班,若是指定時間爲0或者不指定,則線程持續等待,知道被中斷(interrupt())或是notify()能夠參與排班。
tips: 由於wait()後的線程處於Blocked不是Runnable因此是不能進入CPU排班
被競爭鎖定的對象調用notify()時,會從對象等待集合中隨機通知一個線程加入排班,再次執行synchronized前,被通知的線程會和其餘線程功能競爭對象鎖;若是調用notifyAll(),因此等待集合中的線程都會參與排班,這些線程會與其餘線程共同競爭對象鎖。線程調用wait()方法進入等待當時間到或notify(),進入排班並取得對象鎖以後再從調用wait()處開始執行。notifyAll()同理
##2.3 並行API
2.3.1 java.util.concurrent包中的Lock、ReadWriteLock、Condition
Lock:
class A { private Lock lock = new ReentrantLock(); // 聲明類全局變量lock 一個對象一個鎖 // 相似於一個對象一個synchronized public void doSome() { lock.lock(); // 上鎖 try { // ... } finally { lock.unlock(); // 必定要釋放鎖 而且爲了釋放鎖成功要放在finally中 } } }
ReentrantLock能夠達到synchronized的做用,若是已經有線程取得Lock對象鎖定,嘗試再次鎖定同一Lock對象是能夠的。鎖定Lock對象能夠調用其lock()方法,只有取得Look對象的鎖定的線程,才能夠往下執行,接觸鎖定調用Lock對象的unlock方法。
Lock接口還定義了tryLock()方法,若是線程調用tryLock()能夠取得鎖定那麼返回true,若沒法取得鎖定不會發生阻斷而是返回false。Lock接口還有isHeldByCurrentThread()方法返回true,false。
ReadWriteLock:
ReentrantReadWriteLock.ReadLock操做lock()方法時,若沒有任何ReentrantReadWriteLock.WriteLock調用lock()方法,也就是沒有任何寫入鎖定的時候,就能夠取得讀取鎖定(讀取鎖能夠同時被多個線程拿到)。 ReentrantReadWriteLock.WriteLock調用lock()方法時,若沒有任何 ReentrantReadWriteLock.ReadLock或ReentrantReadWriteLock.WriteLock調用過lock()方法,也就是沒有任何讀取或者寫入鎖定的時,才能夠取得寫入鎖。
讀鎖可多線程,在獲取讀鎖以前沒有任何寫鎖才能獲取讀鎖。 在沒有任何讀鎖和寫鎖的時候才能獲取寫鎖。使用讀寫鎖使得讀寫操做分離,增長讀取效率。
class A<E> { private ReadWriteLock lock = new ReentrantReadWriteLock(); private E data; public E get() { // lock.readLock() 返回 ReentrantReadWriteLock.ReadLock // ReentrantReadWriteLock.ReadLock readLock = (ReentrantReadWriteLock.ReadLock) lock.readLock(); lock.readLock().lock(); try { return data; } finally { lock.readLock().unlock(); } } public void set(E e) { // lock.writeLock() 返回 ReentrantReadWriteLock.WriteLock // ReentrantReadWriteLock.WriteLock writeLock = (ReentrantReadWriteLock.WriteLock) lock.writeLock(); lock.writeLock().lock(); try { this.data = e; } finally { lock.writeLock().unlock(); } } }
StampedLock:
ReadWriteLock在沒有任何讀取或寫入鎖定時,才能夠取得寫入鎖定,這能夠用於實現悲觀讀取(Pessimistic Reading)。
然而當讀取線程多,寫入線程少的時候,使用ReadWriteLock可能使得寫線程處於Starvation(飢餓)狀態,由於寫入鎖可能遲遲沒法競爭到鎖,而處於等待狀態。此時JDK8中增長了StampedLock類,可支持樂觀讀取(Optimistic Reading),也就是當讀取線程多,寫入線程少,能夠樂觀的認爲寫入與讀取同時發生的機會較少。所以不悲觀地使用徹底的讀取鎖定,程序能夠查看數據讀取以後,是否遭到寫入線程的變動,再採起後續措施(從新讀取變動後的數據,或者拋出例外)。
class A<E> { private StampedLock lock = new StampedLock(); private E data; public E get() { long stamp = lock.tryOptimisticRead(); // 試着樂觀讀取鎖(不會真正執行讀取鎖定) // 1 返回stamp給validate用 2 若是已經有排他鎖返回0 E res = data; if (!lock.validate(stamp)) { // 查詢是否有排他所的鎖定 1 沒有排他鎖返回true 2 stamp是0的話返回false // 3 stamp表示當前已持有的鎖返回true 4 戳記stamp被其餘排他鎖獲取返回false stamp = lock.readLock(); // 使用readLock()作真正的讀取鎖定 try { res = data; // 在鎖定的狀況下更新局部變量 } finally { lock.unlockRead(stamp); // 解除讀取鎖定 } } return res; // 沒有其餘排他鎖直接返回變量 } public void set(E e) { long stamp = lock.writeLock(); try { this.data = e; } finally { lock.unlockWrite(stamp); } } }
tips: 在validate()以後發生寫入而返回結果不一致是有可能的,若是此時須要保證數據一致性,那麼應該使用悲觀讀取。
Condition:
Condition接口用來搭配Lock,實現與Object的wait(),notify(),notifyAll()相同的效果。分別是Condition的await(),signal(),signalAll()。 當多線程操做統一對象的時候,Object的wait()會讓不一樣類型的線程多在此對象的等待集合中等待,當notify()通知的時候是隨機通知的。但使用Conditon咱們能夠建立多個Condition對象,當使用await()方法那麼就能夠到指定的Condition的等待集合中,通知的時候也能夠到指定的Condition對象的等待集合中通知。
class A<E> { private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); }
Executor:
Runnable用來定義可執行流程與可以使用數據,Thread用來執行Runnable。將Runnable指定給Thread建立用,並調用start()方法。 java.util.concurrent.Executor接口,出如今JDK1.5,目的是將Runnable的指定與實際如何執行分離。
Java線程池的操做方法定義在Executor的子接口ExecutorService(java.util.concurrent.ExecutorService)中,由抽象類AbstractExecutor實現,若是須要使用線程池功能的話可使用其子類java.util.concurrent.ThreadPoolExecutor,ThreadPoolExecutor有多種構造方法。
class A { public void doSome(Executor executor) { System.out.println("執行邏輯"); } public static void main(String[] args) { // 使用newFixedThreadPool建立線程的時候指定線程數量 // Executors.newFixedThreadPool(5); // java.util.concurrent.Executors 的 newCachedThreadPool() 建立ThreadPoolExecutor // 使用上面的方法建立線程池會在必要的時候創建,Runnable可能執行在新建的線程上,也可能在重複利用的線程中 ExecutorService executorService = Executors.newCachedThreadPool(); new A().doSome(executorService); // shutdown()方法會在指定執行的Runnable都完成後將ExecutorService(此處是ThreadPoolExecutor)關閉 executorService.shutdown(); } }
IDEA不推薦上面兩種建立方法理由如圖。
Future與Callable:
ExecutorService中的submit(),invokeAll(),invokeAny()中用到了java.util.concurrent.Future以及Callable。
Future:將想執行的工做交給Future,Future會使用另一個線程來工做,此時你能夠去忙別的事情,過些時候再調用Future.get()方法取得結果,若是結果產生了,get()直接返回結果,不然進入阻斷狀態直到結果返回。get()還能夠指定等待結果的時間,若時間到還未產生結果則拋出java.util.concurrent.TimeoutException,也可使用Future的isDone()方法查看是否有結果產生。
Future常常與Callable搭配使用,Callable與Runnable類似但有兩點不一樣Runnable的run()方法沒法產生返回值,沒法拋出收檢異常(如Thread.sleep的異常必定要在run()中try catch。FutureTask建立的時候能夠接受Runnable或者Cabble兩種。
class A { static String getStr() throws Exception { Thread.sleep(5000); return "Lambda表達式的測試方法執行完成"; } public static void main(String[] args) throws Exception{ FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() { @Override public String call() throws Exception { Thread.sleep(8000); return "內部匿名類的測試方法執行完成"; } }); FutureTask<String> futureTask2 = new FutureTask<String>(A::getStr); // FutureTask也實現了Runnable接口因此也能夠給Thread建立實力用 new Thread(futureTask).start(); new Thread(futureTask2).start(); while (!(futureTask.isDone() && futureTask2.isDone())) { System.out.println("我去幹其餘事情了"); } System.out.println(">>>>兩個FutureTask都完成了" + "FutureTask1:" + futureTask.get() + "FutureTask2:" + futureTask2.get()); } }
使用線程池的submit方法返回Future讓我稍後取得結果。
class A { static String setChicken(int num) throws InterruptedException { // 用線程休眠10秒錶示 老闆再作30份烤雞 Thread.sleep(5000); return "老闆作了" + num + "只烤雞"; } public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); System.out.println("老闆我要30份烤雞"); // ExecutorService的submit()方法接口Callable對象,調用後返回Future對象 // 爲了讓你在稍後能夠取得運算結果 // 這邊作30個烤鴨比上面作30個烤雞快,上面是Thread.start()後執行Future,Future使用 // 單個新線程來作30個烤雞,但這邊使用線程池,至關於好多個線程一塊兒作30個烤雞 Future<String> futureTask = service.submit(() -> setChicken(30)); while (!futureTask.isDone()) { System.out.println("我去幹其餘的了"); } System.out.println("老闆的30份烤雞好了"); } }
ScheduledThreadPoolExecutor:
ScheduledExecutorService的實現類ScheduledThreadPoolExecutor而且繼承ThreadPoolExecutor,具備線程池和排與線程排程功能。 可使用Executors。
class A { public static void main(String[] args) { // 單個線程排程 ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); service.scheduleWithFixedDelay(() -> { System.out.println(new Date()); try { Thread.sleep(2000); // 假設這個工做會進行兩秒 } catch (InterruptedException e) { e.printStackTrace(); } }, 2000, 1000, TimeUnit.MILLISECONDS); // 線程進入後等待2S開始排程工做 // 上一個線程結束後等待1S繼續下個線程 service.scheduleAtFixedRate(() -> { System.out.println(new Date()); try { Thread.sleep(2000); // 假設這個工做會進行兩秒 } catch (InterruptedException e) { e.printStackTrace(); } }, 2000, 1000, TimeUnit.MILLISECONDS); // 線程進入後等待2S開始排程工做 // 上一個線程結束後等待1S繼續下個線程,若是下個線程在1S後沒完成,那麼等線程完成工做後直接執行 // 下一個線程 } }
ForkJoinPool:
Future的一個實現類java.util.concurrent.ForkJoinTask及其子類有ExecutorService另外一個實現類java.util.concurrent.ForkJoinPool有關,他們主要用來解決(Divide and Conquer) 分而治之的問題。 ForkJoinTask在ForkJoinPool管理的時候執行fork()方法,則會以另一個線程來執行他,調用join()取得結果,無結果則等待至結果返回。通常使用ForkJoinTask的子類RecursiveTask和RecursiveAction, 用類繼承兩個的其中一個,有返回值用RecursiveTask,沒返回值用 RecursiveAction,調用的時候使用他們的compute方法。
class A extends RecursiveTask<Long> { private long n; public A(long n) { this.n = n; } @Override protected Long compute() { // 操做compute()方法 將子任務的分解和求解放到compute()中 if (n <= 20) { // 避免分解出過多的子任務形成負擔。 return usualSolve(n); // 小於20的不分解,直接循環運算 } // 分解出n-1子任務請ForkJoinPool分配線程來執行 ForkJoinTask<Long> subTask = new A(n - 1).fork(); // 分解n-2子任務而且直接運行 + 取得子任務執行結果。 return new A(n - 2).compute() + subTask.join(); } private long usualSolve(long n) { if (n <= 1) { return n; } return usualSolve(n - 1) + usualSolve(n - 2); } public static void main(String[] args) { A a = new A(45); ForkJoinPool pool = new ForkJoinPool(); // 全部ForkJoinTask實力的compute()方法執行完以後,ForkJoinPool就會關閉 System.out.println(pool.invoke(a)); // 開始分而治之 } }