簡言之,進程可視爲一個正在運行的程序。它是系統運行程序的基本單位,所以進程是動態的。進程是具備必定獨立功能的程序關於某個數據集合上的一次運行活動。進程是操做系統進行資源分配的基本單位。html
線程是操做系統進行調度的基本單位。線程也叫輕量級進程(Light Weight Process),在一個進程裏能夠建立多個線程,這些線程都擁有各自的計數器、堆棧和局部變量等屬性,而且可以訪問共享的內存變量。java
線程(Thread
)基本方法清單:git
方法 | 描述 |
---|---|
run |
線程的執行實體。 |
start |
線程的啓動方法。 |
currentThread |
返回對當前正在執行的線程對象的引用。 |
setName |
設置線程名稱。 |
getName |
獲取線程名稱。 |
setPriority |
設置線程優先級。Java 中的線程優先級的範圍是 [1,10],通常來講,高優先級的線程在運行時會具備優先權。能夠經過 thread.setPriority(Thread.MAX_PRIORITY) 的方式設置,默認優先級爲 5。 |
getPriority |
獲取線程優先級。 |
setDaemon |
設置線程爲守護線程。 |
isDaemon |
判斷線程是否爲守護線程。 |
isAlive |
判斷線程是否啓動。 |
interrupt |
中斷另外一個線程的運行狀態。 |
interrupted |
測試當前線程是否已被中斷。經過此方法能夠清除線程的中斷狀態。換句話說,若是要連續調用此方法兩次,則第二次調用將返回 false(除非當前線程在第一次調用清除其中斷狀態以後且在第二次調用檢查其狀態以前再次中斷)。 |
join |
可使一個線程強制運行,線程強制運行期間,其餘線程沒法運行,必須等待此線程完成以後才能夠繼續執行。 |
Thread.sleep |
靜態方法。將當前正在執行的線程休眠。 |
Thread.yield |
靜態方法。將當前正在執行的線程暫停,讓其餘線程執行。 |
建立線程有三種方式:程序員
Thread
類Runnable
接口Callable
接口經過繼承 Thread
類建立線程的步驟:github
Thread
類的子類,並覆寫該類的 run
方法。run
方法的方法體就表明了線程要完成的任務,所以把 run
方法稱爲執行體。Thread
子類的實例,即建立了線程對象。start
方法來啓動該線程。public class ThreadDemo { public static void main(String[] args) { // 實例化對象 MyThread tA = new MyThread("Thread 線程-A"); MyThread tB = new MyThread("Thread 線程-B"); // 調用線程主體 tA.start(); tB.start(); } static class MyThread extends Thread { private int ticket = 5; MyThread(String name) { super(name); } @Override public void run() { while (ticket > 0) { System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票"); ticket--; } } } } 複製代碼
實現 Runnable
接口優於繼承 Thread
類,由於:編程
Thread
類就沒法繼承其它類,這不利於擴展。Thread
類開銷過大。經過實現 Runnable
接口建立線程的步驟:安全
Runnable
接口的實現類,並覆寫該接口的 run
方法。該 run
方法的方法體一樣是該線程的線程執行體。Runnable
實現類的實例,並以此實例做爲 Thread
的 target 來建立 Thread
對象,該 Thread
對象纔是真正的線程對象。start
方法來啓動該線程。public class RunnableDemo { public static void main(String[] args) { // 實例化對象 Thread tA = new Thread(new MyThread(), "Runnable 線程-A"); Thread tB = new Thread(new MyThread(), "Runnable 線程-B"); // 調用線程主體 tA.start(); tB.start(); } static class MyThread implements Runnable { private int ticket = 5; @Override public void run() { while (ticket > 0) { System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票"); ticket--; } } } } 複製代碼
繼承 Thread 類 和 實現 Runnable 接口這兩種建立線程的方式都沒有返回值。因此,線程執行完後,沒法獲得執行結果。但若是指望獲得執行結果該怎麼作?markdown
爲了解決這個問題,Java 1.5 後,提供了
Callable
接口和Future
接口,經過它們,能夠在線程執行結束後,返回執行結果。網絡
經過實現 Callable
接口建立線程的步驟:併發
Callable
接口的實現類,並實現 call
方法。該 call
方法將做爲線程執行體,而且有返回值。Callable
實現類的實例,使用 FutureTask
類來包裝 Callable
對象,該 FutureTask
對象封裝了該 Callable
對象的 call
方法的返回值。FutureTask
對象做爲 Thread
對象的 target 建立並啓動新線程。FutureTask
對象的 get
方法來得到線程執行結束後的返回值。public class CallableDemo { public static void main(String[] args) { Callable<Long> callable = new MyThread(); FutureTask<Long> future = new FutureTask<>(callable); new Thread(future, "Callable 線程").start(); try { System.out.println("任務耗時:" + (future.get() / 1000000) + "毫秒"); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } static class MyThread implements Callable<Long> { private int ticket = 10000; @Override public Long call() { long begin = System.nanoTime(); while (ticket > 0) { System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票"); ticket--; } long end = System.nanoTime(); return (end - begin); } } } 複製代碼
start
和 run
方法有什麼區別run
方法是線程的執行體。start
方法會啓動線程,而後 JVM 會讓這個線程去執行 run
方法。Thread
類的 run
方法麼Thread
的 run
方法,它的行爲就會和普通的方法同樣。Thread
的 start
方法。使用 Thread.sleep
方法可使得當前正在執行的線程進入休眠狀態。
使用 Thread.sleep
須要向其傳入一個整數值,這個值表示線程將要休眠的毫秒數。
Thread.sleep
方法可能會拋出 InterruptedException
,由於異常不能跨線程傳播回 main
中,所以必須在本地進行處理。線程中拋出的其它異常也一樣須要在本地進行處理。
public class ThreadSleepDemo { public static void main(String[] args) { new Thread(new MyThread("線程A", 500)).start(); new Thread(new MyThread("線程B", 1000)).start(); new Thread(new MyThread("線程C", 1500)).start(); } static class MyThread implements Runnable { /** 線程名稱 */ private String name; /** 休眠時間 */ private int time; private MyThread(String name, int time) { this.name = name; this.time = time; } @Override public void run() { try { // 休眠指定的時間 Thread.sleep(this.time); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.name + "休眠" + this.time + "毫秒。"); } } } 複製代碼
Thread.yield
方法的調用聲明瞭當前線程已經完成了生命週期中最重要的部分,能夠切換給其它線程來執行 。
該方法只是對線程調度器的一個建議,並且也只是建議具備相同優先級的其它線程能夠運行。
public class ThreadYieldDemo { public static void main(String[] args) { MyThread t = new MyThread(); new Thread(t, "線程A").start(); new Thread(t, "線程B").start(); } static class MyThread implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "運行,i = " + i); if (i == 2) { System.out.print("線程禮讓:"); Thread.yield(); } } } } } 複製代碼
Thread
中的stop
方法有缺陷,已廢棄。使用
Thread.stop
中止線程會致使它解鎖全部已鎖定的監視器(因爲未經檢查的ThreadDeath
異常會在堆棧中傳播,這是天然的結果)。 若是先前由這些監視器保護的任何對象處於不一致狀態,則損壞的對象將對其餘線程可見,從而可能致使任意行爲。Thread.stop
的許多用法應由僅修改某些變量以指示目標線程應中止運行的代碼代替。 目標線程應按期檢查此變量,若是該變量指示要中止運行,則應按有序方式從其運行方法返回。若是目標線程等待很長時間(例如,在條件變量上),則應使用中斷方法來中斷等待。
當一個線程運行時,另外一個線程能夠直接經過 interrupt
方法中斷其運行狀態。
public class ThreadInterruptDemo { public static void main(String[] args) { MyThread mt = new MyThread(); // 實例化Runnable子類對象 Thread t = new Thread(mt, "線程"); // 實例化Thread對象 t.start(); // 啓動線程 try { Thread.sleep(2000); // 線程休眠2秒 } catch (InterruptedException e) { System.out.println("三、休眠被終止"); } t.interrupt(); // 中斷線程執行 } static class MyThread implements Runnable { @Override public void run() { System.out.println("一、進入run()方法"); try { Thread.sleep(10000); // 線程休眠10秒 System.out.println("二、已經完成了休眠"); } catch (InterruptedException e) { System.out.println("三、休眠被終止"); return; // 返回調用處 } System.out.println("四、run()方法正常結束"); } } } 複製代碼
若是一個線程的 run
方法執行一個無限循環,而且沒有執行 sleep
等會拋出 InterruptedException
的操做,那麼調用線程的 interrupt
方法就沒法使線程提早結束。
可是調用 interrupt
方法會設置線程的中斷標記,此時調用 interrupted
方法會返回 true
。所以能夠在循環體中使用 interrupted
方法來判斷線程是否處於中斷狀態,從而提早結束線程。
安全地終止線程有兩種方法:
volatile
標誌位,在 run
方法中使用標誌位控制線程終止interrupt
方法和 Thread.interrupted
方法配合使用來控制線程終止示例:使用 volatile
標誌位控制線程終止
public class ThreadStopDemo2 { public static void main(String[] args) throws Exception { MyTask task = new MyTask(); Thread thread = new Thread(task, "MyTask"); thread.start(); TimeUnit.MILLISECONDS.sleep(50); task.cancel(); } private static class MyTask implements Runnable { private volatile boolean flag = true; private volatile long count = 0L; @Override public void run() { System.out.println(Thread.currentThread().getName() + " 線程啓動"); while (flag) { System.out.println(count++); } System.out.println(Thread.currentThread().getName() + " 線程終止"); } /** * 經過 volatile 標誌位來控制線程終止 */ public void cancel() { flag = false; } } } 複製代碼
示例:使用 interrupt
方法和 Thread.interrupted
方法配合使用來控制線程終止
public class ThreadStopDemo3 { public static void main(String[] args) throws Exception { MyTask task = new MyTask(); Thread thread = new Thread(task, "MyTask"); thread.start(); TimeUnit.MILLISECONDS.sleep(50); thread.interrupt(); } private static class MyTask implements Runnable { private volatile long count = 0L; @Override public void run() { System.out.println(Thread.currentThread().getName() + " 線程啓動"); // 經過 Thread.interrupted 和 interrupt 配合來控制線程終止 while (!Thread.interrupted()) { System.out.println(count++); } System.out.println(Thread.currentThread().getName() + " 線程終止"); } } } 複製代碼
什麼是守護線程?
爲何須要守護線程?
如何使用守護線程?
isDaemon
方法判斷線程是否爲守護線程。setDaemon
方法設置線程爲守護線程。
setDaemon
必須在 thread.start
方法以前設置,不然會拋出 llegalThreadStateException
異常;public class ThreadDaemonDemo { public static void main(String[] args) { Thread t = new Thread(new MyThread(), "線程"); t.setDaemon(true); // 此線程在後臺運行 System.out.println("線程 t 是不是守護進程:" + t.isDaemon()); t.start(); // 啓動線程 } static class MyThread implements Runnable { @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + "在運行。"); } } } } 複製代碼
參考閱讀:Java 中守護線程的總結
yield
方法
yield
方法會 讓線程從 Running
狀態轉入 Runnable
狀態。yield
方法後,只有與當前線程相同或更高優先級的Runnable
狀態線程纔會得到執行的機會。sleep
方法
sleep
方法會 讓線程從 Running
狀態轉入 Waiting
狀態。sleep
方法須要指定等待的時間,超過等待時間後,JVM 會將線程從 Waiting
狀態轉入 Runnable
狀態。sleep
方法後,不管什麼優先級的線程均可以獲得執行機會。sleep
方法不會釋放「鎖標誌」,也就是說若是有 synchronized
同步塊,其餘線程仍然不能訪問共享數據。join
join
方法會 讓線程從 Running
狀態轉入 Waiting
狀態。join
方法後,當前線程必須等待調用 join
方法的線程結束後才能繼續執行。Thread
類的 sleep
和 yield
方法將處理 Running
狀態的線程。
因此在其餘處於非 Running
狀態的線程上執行這兩個方法是沒有意義的。這就是爲何這些方法是靜態的。它們能夠在當前正在執行的線程中工做,並避免程序員錯誤的認爲能夠在其餘非運行線程調用這些方法。
即便設置了線程的優先級,也沒法保證高優先級的線程必定先執行。
緣由在於線程優先級依賴於操做系統的支持,然而,不一樣的操做系統支持的線程優先級並不相同,不能很好的和 Java 中線程優先級一一對應。
當多個線程能夠一塊兒工做去解決某個問題時,若是某些部分必須在其它部分以前完成,那麼就須要對線程進行協調。
wait
- wait
方法使得線程釋放其佔有的對象鎖,讓線程從 Running
狀態轉入 Waiting
狀態,並等待 notify
/ notifyAll
來喚醒 。若是沒有釋放鎖,那麼其它線程就沒法進入對象的同步方法或者同步控制塊中,那麼就沒法執行 notify
或者 notifyAll
來喚醒掛起的線程,形成死鎖。notify
- 喚醒一個正在 Waiting
狀態的線程,並讓它拿到對象鎖,具體喚醒哪個線程由 JVM 控制 。notifyAll
- 喚醒全部正在 Waiting
狀態的線程,接下來它們須要競爭對象鎖。注意:
wait
、notify
、notifyAll
都是Object
類中的方法,而非Thread
。wait
、notify
、notifyAll
只能用在synchronized
方法或者synchronized
代碼塊中使用,不然會在運行時拋出IllegalMonitorStateException
。爲何
wait
、notify
、notifyAll
不定義在Thread
中?爲何wait
、notify
、notifyAll
要配合synchronized
使用?首先,須要瞭解幾個基本知識點:
- 每個 Java 對象都有一個與之對應的 監視器(monitor)
- 每個監視器裏面都有一個 對象鎖 、一個 等待隊列、一個 同步隊列
瞭解了以上概念,咱們回過頭來理解前面兩個問題。
爲何這幾個方法不定義在
Thread
中?因爲每一個對象都擁有對象鎖,讓當前線程等待某個對象鎖,天然應該基於這個對象(
Object
)來操做,而非使用當前線程(Thread
)來操做。由於當前線程可能會等待多個線程的鎖,若是基於線程(Thread
)來操做,就很是複雜了。爲何
wait
、notify
、notifyAll
要配合synchronized
使用?若是調用某個對象的
wait
方法,當前線程必須擁有這個對象的對象鎖,所以調用wait
方法必須在synchronized
方法和synchronized
代碼塊中。
生產者、消費者模式是 wait
、notify
、notifyAll
的一個經典使用案例:
public class ThreadWaitNotifyDemo02 { private static final int QUEUE_SIZE = 10; private static final PriorityQueue<Integer> queue = new PriorityQueue<>(QUEUE_SIZE); public static void main(String[] args) { new Producer("生產者A").start(); new Producer("生產者B").start(); new Consumer("消費者A").start(); new Consumer("消費者B").start(); } static class Consumer extends Thread { Consumer(String name) { super(name); } @Override public void run() { while (true) { synchronized (queue) { while (queue.size() == 0) { try { System.out.println("隊列空,等待數據"); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); queue.notifyAll(); } } queue.poll(); // 每次移走隊首元素 queue.notifyAll(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 從隊列取走一個元素,隊列當前有:" + queue.size() + "個元素"); } } } } static class Producer extends Thread { Producer(String name) { super(name); } @Override public void run() { while (true) { synchronized (queue) { while (queue.size() == QUEUE_SIZE) { try { System.out.println("隊列滿,等待有空餘空間"); queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); queue.notifyAll(); } } queue.offer(1); // 每次插入一個元素 queue.notifyAll(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 向隊列取中插入一個元素,隊列當前有:" + queue.size() + "個元素"); } } } } } 複製代碼
在線程操做中,可使用 join
方法讓一個線程強制運行,線程強制運行期間,其餘線程沒法運行,必須等待此線程完成以後才能夠繼續執行。
public class ThreadJoinDemo { public static void main(String[] args) { MyThread mt = new MyThread(); // 實例化Runnable子類對象 Thread t = new Thread(mt, "mythread"); // 實例化Thread對象 t.start(); // 啓動線程 for (int i = 0; i < 50; i++) { if (i > 10) { try { t.join(); // 線程強制運行 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Main 線程運行 --> " + i); } } static class MyThread implements Runnable { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName() + " 運行,i = " + i); // 取得當前線程的名字 } } } } 複製代碼
管道輸入/輸出流和普通的文件輸入/輸出流或者網絡輸入/輸出流不一樣之處在於,它主要用於線程之間的數據傳輸,而傳輸的媒介爲內存。 管道輸入/輸出流主要包括了以下 4 種具體實現:PipedOutputStream
、PipedInputStream
、PipedReader
和 PipedWriter
,前兩種面向字節,然後兩種面向字符。
public class Piped { public static void main(String[] args) throws Exception { PipedWriter out = new PipedWriter(); PipedReader in = new PipedReader(); // 將輸出流和輸入流進行鏈接,不然在使用時會拋出IOException out.connect(in); Thread printThread = new Thread(new Print(in), "PrintThread"); printThread.start(); int receive = 0; try { while ((receive = System.in.read()) != -1) { out.write(receive); } } finally { out.close(); } } static class Print implements Runnable { private PipedReader in; Print(PipedReader in) { this.in = in; } public void run() { int receive = 0; try { while ((receive = in.read()) != -1) { System.out.print((char) receive); } } catch (IOException e) { e.printStackTrace(); } } } } 複製代碼
java.lang.Thread.State
中定義了 6 種不一樣的線程狀態,在給定的一個時刻,線程只能處於其中的一個狀態。
如下是各狀態的說明,以及狀態間的聯繫:
新建(New) - 還沒有調用 start
方法的線程處於此狀態。此狀態意味着:建立的線程還沒有啓動。
可運行(Runnable) - 已經調用了 start
方法的線程處於此狀態。此狀態意味着:線程已經在 JVM 中運行。可是在操做系統層面,它可能處於運行狀態,也可能等待資源調度(例如處理器資源),資源調度完成就進入運行狀態。因此該狀態的可運行是指能夠被運行,具體有沒有運行要看底層操做系統的資源調度。
阻塞(Blocked) - 請求獲取 monitor lock 從而進入 synchronized
函數或者代碼塊,可是其它線程已經佔用了該 monitor lock,因此處於阻塞狀態。要結束該狀態進入 Runnable
,從而須要其餘線程釋放 monitor lock。此狀態意味着:線程處於被阻塞狀態。
等待(Waiting) - 此狀態意味着:線程等待被其餘線程顯式地喚醒。 阻塞和等待的區別在於,阻塞是被動的,它是在等待獲取 monitor lock。而等待是主動的,經過調用 Object.wait
等方法進入。
進入方法 | 退出方法 |
---|---|
沒有設置 Timeout 參數的 Object.wait 方法 |
Object.notify / Object.notifyAll |
沒有設置 Timeout 參數的 Thread.join 方法 |
被調用的線程執行完畢 |
LockSupport.park 方法 |
LockSupport.unpark |
定時等待(Timed waiting) - 此狀態意味着:無需等待其它線程顯式地喚醒,在必定時間以後會被系統自動喚醒。
進入方法 | 退出方法 |
---|---|
Thread.sleep 方法 |
時間結束 |
設置了 Timeout 參數的 Object.wait 方法 |
時間結束 / Object.notify / Object.notifyAll |
設置了 Timeout 參數的 Thread.join 方法 |
時間結束 / 被調用的線程執行完畢 |
LockSupport.parkNanos 方法 |
LockSupport.unpark |
LockSupport.parkUntil 方法 |
LockSupport.unpark |
終止(Terminated) - 線程 run
方法執行結束,或者因異常退出了 run
方法。此狀態意味着:線程結束了生命週期。