該篇文章做爲本身併發學習的一個開始,首先介紹一下線程的概念以及使用。html
討論基於單核cpu進行java
要了解線程的意義,首先先介紹一下進程,什麼是進程?進程概念以下(摘自百度百科):算法
狹義定義:進程是正在運行的程序的實例(an instance of a computer program that is being executed)。多線程
廣義定義:進程是一個具備必定獨立功能的程序關於某個數據集合的一次運行活動。它是操做系統動態執行的基本單元,在傳統的操做系統中,進程既是基本的分配單元,也是基本的執行單元。併發
簡單的理解就是一個Andorid的App(內部不引入多進程狀況下)應用程序表明的一個進程,該進程在系統中擁有本身的內存空間,全部進程互相獨立,互不影響。經過進程這個抽象的概念,使得操做系統中運行不一樣的應用程序成爲了可能。進程有三個狀態:oracle
進程的狀態在上面三個狀態中轉換,假設系統中總共有3個進程針對單核cpu的狀況,那麼一個時間片內只會有一個進程處於運行態,其餘的進程分別處於就緒態和阻塞態,具體的轉換過程以下圖所示:ide
進程的概念大致介紹到這,那麼既然提出來了進程,爲何又要提出線程的概念呢?試想但一個進程中存在多任務,好比在進程中輸入過程當中須要讀取歷史記錄,同時還要響應用戶的輸入事件,那麼在進程中只能串行的進行,如何進行並行就是線程出現的意義了。學習
線程的概念以下(摘自百度百科):測試
線程,有時被稱爲輕量進程(Lightweight Process,LWP),是程序執行流的最小單元。一個標準的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程本身不擁有系統資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的所有資源。this
線程是CPU調度的基本單位,在Java中一個進程至少有一個線程,多線程的概念其實是對於系統時間片搶佔的表現。對於單核cpu而言,同一時間內仍是隻能容許一個線程的執行,經過輪流切換使用時間片,達到併發的概念。講到併發,並行跟併發的概念一塊兒記錄一下,用一張圖表示一下兩則的區別:
Erlang 之父 Joe Armstrong提供的一張解釋圖,能夠主觀理解一下。併發指的是一個處理器同時處理多個任務,這種處理方式經過輪流得到時間片方式進行,而並行是併發的子集,並行指的是多個處理器或者是多核的處理器同時處理多個不一樣的任務。
線程在Java中的實現經過以下三種方式:
建立簡單調用Demo以下:
class ThreadTest { private Runnable mRunnable = () -> System.out.println("from runnable"); private class TestThread extends Thread { @Override public void run() { super.run(); System.out.println("from Thread"); } } private Callable<Void> mCallable = () -> { System.out.println("from Callable"); return null; }; void testThread() { new TestThread().run(); new Thread(mRunnable).run(); FutureTask<Void> task = new FutureTask<Void>(mCallable); new Thread(task).run(); } } ----- 輸出結果: from Thread from runnable from Callable
上面經過thread.start()
調用啓動了線程,線程在執行過程當中是有生命週期的從出生到死亡,其生命週期以下圖片(來自菜鳥教程)所示:
關於各個方法對應生命週期的圖以下:
接下來看下線程中的一些重要方法:
注:Thread中suspend()和resume()兩個方法在JDK1.5中已經廢除,再也不介紹。由於有死鎖傾向。
Thread.sleep(long millis)使線程從運行狀態轉到阻塞狀態。millis參數設定睡眠的時間,以毫秒爲單位。經過該方法可以使得當前線程暫停執行,讓出cpu資源給其餘的須要的使用,這時候狀態爲BLOCK狀態,必定時間後進入就緒狀態,準備從新獲取cpu。**須要注意的是若是當前線程進入了同步鎖,sleep方法並不會釋放鎖,即便當前線程使用sleep方法讓出了CPU,但其餘被同步鎖擋住了的線程也沒法獲得執行。**在sleep()休眠時間事後,該線程也不必定會立刻執行,這是由於其它線程可能正在運行並且沒有被調度爲放棄執行,除非此線程具備更高的優先級。
這裏須要區分sleep和wait的區別,wait和notify方法跟sychronized關鍵字一塊兒配套使用,wait()方法在進入等待狀態的時候,這個時候會讓度出cpu資源讓其餘線程使用,與sleep()不一樣的是,這個時候wait()方法是不佔有對應的鎖的.
首先看下不使用同步鎖時候的代碼:
void testThread() { TestThread thread = new TestThread(); thread.start(); new Thread(mRunnable).start(); } //輸出結果 2018-11-09 18:52:26 PM : from Thread start run 2018-11-09 18:52:26 PM : from runnable start run 2018-11-09 18:52:26 PM : from runnable start end 2018-11-09 18:52:29 PM : from Thread end
當咱們當用sleep()方法時候確實是會釋放出cpu資源的。那麼繼續確認一下使用同步鎖的狀況:
private class TestThread extends Thread { @Override public void run() { super.run(); synchronized (ThreadTest.class) { println("from Thread start run"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } println("from Thread end"); } } } void testThread() { TestThread thread = new TestThread(); thread.start(); new Thread(mRunnable).start(); } private Runnable mRunnable = () -> { synchronized (ThreadTest.class) { println("from runnable start run"); println("from runnable start end"); } }; //輸出結果 2018-11-09 18:54:46 PM : from Thread start run 2018-11-09 18:54:49 PM : from Thread end 2018-11-09 18:54:49 PM : from runnable start run 2018-11-09 18:54:49 PM : from runnable start end
上面的輸出結果驗證了咱們的結論的正確性。
####Thread.join(long millis)
join()方法容許一個線程A等待另外一個線程B執行完畢後再執行,也就是說具體使用以下所示:
void testThread() { TestThread thread = new TestThread(); thread.start(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("main thread executed"); } ---- //輸出結果 2018-11-09 18:30:14 PM : from Thread start run 2018-11-09 18:30:17 PM : from Thread end main thread executed
能夠看到主線程會等待TestThread線程執行完畢後在打印出log。主線程在調用thread.join()方法後進入了阻塞狀態,等到thread執行完畢後恢復就緒狀態,等待cpu從新調用。
須要注意的是join()方法實現是經過Object.wait()方法。 當main線程調用thread.join時候,main線程會得到線程對象thread的鎖(wait 意味着拿到該對象的鎖),調用該對象的wait(等待時間),直到該對象喚醒main線程 ,好比退出後。這就意味着main 線程調用thread.join時,必須可以拿到線程thread對象的鎖,詳細可查閱該篇文章。
該方法的表示該線程讓出cpu資源,**從運行態直接轉換爲就緒態。**通俗的將就是說當一個線程A使用了這個yield()方法以後,它就會把本身CPU執行的時間讓掉,cpu經過調度算法從新讓A或者其它的線程運行。
示例Demo以下:
private class TestThread extends Thread { @Override public void run() { super.run(); for (int i = 0; i < 10; i++) { println("TestThread print" + (i + 1) + " start"); if (i == 3) { yield(); } } } } void testThread() { TestThread thread = new TestThread(); thread.start(); new Thread(mRunnable).start(); } private Runnable mRunnable = () -> { for (int i = 0; i < 10; i++) { println("Runnable print" + (i + 1) + " start"); } }; //輸出結果 2018-11-09 19:36:29 PM : Runnable print1 start 2018-11-09 19:36:29 PM : TestThread print1 start 2018-11-09 19:36:29 PM : Runnable print2 start 2018-11-09 19:36:29 PM : TestThread print2 start 2018-11-09 19:36:29 PM : Runnable print3 start 2018-11-09 19:36:29 PM : TestThread print3 start 2018-11-09 19:36:29 PM : Runnable print4 start 2018-11-09 19:36:29 PM : TestThread print4 start 2018-11-09 19:36:29 PM : Runnable print5 start 2018-11-09 19:36:29 PM : Runnable print6 start 2018-11-09 19:36:29 PM : Runnable print7 start 2018-11-09 19:36:29 PM : Runnable print8 start 2018-11-09 19:36:29 PM : Runnable print9 start 2018-11-09 19:36:29 PM : Runnable print10 start 2018-11-09 19:36:29 PM : TestThread print5 start 2018-11-09 19:36:29 PM : TestThread print6 start 2018-11-09 19:36:29 PM : TestThread print7 start 2018-11-09 19:36:29 PM : TestThread print8 start 2018-11-09 19:36:29 PM : TestThread print9 start 2018-11-09 19:36:29 PM : TestThread print10 start
當TestThread中的i等於3的時候能夠看見確實TestThread讓出cpu讓Runnable中的多執行了一會。
在使用線程的時候,JDK爲咱們提供了中斷線程的操做:interrupt方法,這裏須要注意,經過interrupt方法只是通知線程A應該中斷了,而中斷與否由線程A自主控制。關於Thread中的各類interrupt主要有三個,接下來依次看下
//靜態方法 public static boolean interrupted() { return currentThread().isInterrupted(true); } /** * Tests if some Thread has been interrupted. The interrupted state * is reset or not based on the value of ClearInterrupted that is * passed. */ private native boolean isInterrupted(boolean ClearInterrupted);
該方法調用當前線程的isInterrupted(boolean ClearInterrupted)
方法,isInterrupted(boolean ClearInterrupted)
方法可以獲得當前線程的狀態,若是傳遞的是true,那麼在返回當前線程狀態後,它會把當前線程的interrupt狀態「復位」,假設當前線程的isInterrupt狀態爲true,它會返回true,但事後isInterrupt的狀態會復位爲false。
//實例方法 public boolean isInterrupted() { return isInterrupted(false); }
該方法爲實例對象調用的方法,與靜態interrupted()
方法相同,惟一區別是參數傳遞的是false,即表示直接返回當前線程的狀態。
public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag b.interrupt(this); return; } } interrupt0(); }
interrupt()方法爲線程中斷的方法,經過該方法可以通知對應的線程能夠進入中斷的狀態了。若是線程在調用wait()方法,join()方法,sleep()方法後阻塞,這時候進行調用interrupt()
方法,那麼線程會拋出InterruptedException異常;若是線程NIO操做中阻塞,則會拋出ClosedByInterruptException異常(因爲作客戶端,這塊不是特別瞭解,寫錯了麻煩糾正一下)。特別注意一點,調用該方法的必定是阻塞的線程來調用。
當咱們在線程內部中有使用可以拋出InterruptedException異常方法時,官方建議咱們經過主動調用thread.interrupt()
方法進行中斷線程的請求;若是線程中沒有,則使用Thread.interrupted()方法以及thread.isInterrupted()
方法進行判斷進行中斷請求。複雜的時候能夠混合使用來進行中斷請求的操做。
接下來就是寫代碼驗證的時候了,首先咱們測試一下調用interrupt()`方法:
public static void main(String[] args) { TestThread testThread = new TestThread(); testThread.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } testThread.interrupt(); } public static class TestThread extends Thread{ @Override public void run() { super.run(); try { Thread.sleep(3000); } catch (InterruptedException e) { System.out.println("interrupt occur"); } } } -------- //輸出結果 interrupt occur
能夠看到,在主線程調用testThread.interrupt()
方法後,TestThread拋出了InterruptedException異常,再試試看join()
方法:
public static void main(String[] args) throws InterruptedException { TestThread testThread = new TestThread(); TestThread1 testThread1 = new TestThread1(testThread); testThread.start(); Thread.sleep(600); testThread1.start(); Thread.sleep(600); testThread1.interrupt(); } public static class TestThread1 extends Thread { private TestThread mThread; public TestThread1(TestThread thread) { mThread=thread; } @Override public void run() { super.run(); try { mThread.join(); System.out.println("executed after TestThread"); } catch (InterruptedException e) { System.out.println("InterruptedException for TestThread"); } System.out.println("do TestThread1 job"); } } public static class TestThread extends Thread { @Override public void run() { super.run(); try { Thread.sleep(3000); } catch (InterruptedException e) { } } } //輸出結果 InterruptedException for TestThread do TestThread1 job
能夠看到,在testThread1正處於阻塞的時候,咱們調用testThread1.interrupt()
後,拋出了InterruptedException 異常,TestThread1沒有等到TestThread執行完畢就直接走本身的任務了。
Ok,接下來再看看interrupted()以及isInterrupted()的驗證過程,這裏寫個死循環來模擬耗時的操做:
public static void main(String[] args) throws InterruptedException { TestThread testThread = new TestThread(); testThread.start(); Thread.sleep(3000); System.out.println("execute testThread.interrupt method"); testThread.interrupt(); } public static class TestThread extends Thread { @Override public void run() { super.run(); int i=1; while (!isInterrupted()) { i=2; } System.out.println("method for is isInterrupted's value="+isInterrupted()); System.out.println("method for is isInterrupted's value2="+isInterrupted()); } } //結果 execute testThread.interrupt method method for is isInterrupted's value=true method for is isInterrupted's value2=true
上面的結果能夠看到isInterrupted()
方法執行得到到的一直是true,再看看interrupted()方法:
public static void main(String[] args) throws InterruptedException { TestThread testThread = new TestThread(); testThread.start(); Thread.sleep(3000); System.out.println("execute testThread.interrupt method"); testThread.interrupt(); } public static class TestThread extends Thread { @Override public void run() { super.run(); int i=1; while (!isInterrupted()) { i=2; } System.out.println("method for is interrupted's value="+Thread.interrupted()); System.out.println("method for is interrupted's value2="+Thread.interrupted()); } } //結果 execute testThread.interrupt method method for is interrupted's value=true method for is interrupted's value2=false
這裏也符合咱們上面獲得的結論,在調用Thread.interrupted()第一次時候返回了true,java內部把標誌位清除爲了false,第二次再去獲取就變成了false。