增城微信網(wǎng)站建設(shè)公司網(wǎng)絡(luò)推廣營銷
文章目錄
- Java 線程基礎(chǔ)
- 線程簡介
- 什么是進程
- 什么是線程
- 進程和線程的區(qū)別
- 創(chuàng)建線程
- Thread
- Runnable
- Callable、Future、FutureTask
- Callable
- Future
- FutureTask
- Callable + Future + FutureTask 示例
- 線程基本用法
- 線程休眠
- 線程禮讓
- 終止線程
- 守護線程
- 線程通信
- wait/notify/notifyAll
- join
- 管道
- 線程生命周期
- 線程常見問題
- sleep、yield、join 方法有什么區(qū)別
- 為什么 sleep 和 yield 方法是靜態(tài)的
- Java 線程是否按照線程優(yōu)先級嚴格執(zhí)行
- 一個線程兩次調(diào)用 start()方法會怎樣
- `start` 和 `run` 方法有什么區(qū)別
- 可以直接調(diào)用 `Thread` 類的 `run` 方法么
- 參考資料
Java 線程基礎(chǔ)
關(guān)鍵詞:
Thread
、Runnable
、Callable
、Future
、wait
、notify
、notifyAll
、join
、sleep
、yeild
、線程狀態(tài)
、線程通信
線程簡介
什么是進程
簡言之,進程可視為一個正在運行的程序。它是系統(tǒng)運行程序的基本單位,因此進程是動態(tài)的。進程是具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合上的一次運行活動。進程是操作系統(tǒng)進行資源分配的基本單位。
什么是線程
線程是操作系統(tǒng)進行調(diào)度的基本單位。線程也叫輕量級進程(Light Weight Process),在一個進程里可以創(chuàng)建多個線程,這些線程都擁有各自的計數(shù)器、堆棧和局部變量等屬性,并且能夠訪問共享的內(nèi)存變量。
進程和線程的區(qū)別
- 一個程序至少有一個進程,一個進程至少有一個線程。
- 線程比進程劃分更細,所以執(zhí)行開銷更小,并發(fā)性更高。
- 進程是一個實體,擁有獨立的資源;而同一個進程中的多個線程共享進程的資源。
創(chuàng)建線程
創(chuàng)建線程有三種方式:
- 繼承
Thread
類 - 實現(xiàn)
Runnable
接口 - 實現(xiàn)
Callable
接口
Thread
通過繼承 Thread
類創(chuàng)建線程的步驟:
- 定義
Thread
類的子類,并覆寫該類的run
方法。run
方法的方法體就代表了線程要完成的任務(wù),因此把run
方法稱為執(zhí)行體。 - 創(chuàng)建
Thread
子類的實例,即創(chuàng)建了線程對象。 - 調(diào)用線程對象的
start
方法來啟動該線程。
public class ThreadDemo {public static void main(String[] args) {// 實例化對象MyThread tA = new MyThread("Thread 線程-A");MyThread tB = new MyThread("Thread 線程-B");// 調(diào)用線程主體tA.start();tB.start();}static class MyThread extends Thread {private int ticket = 5;MyThread(String name) {super(name);}@Overridepublic void run() {while (ticket > 0) {System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票");ticket--;}}}}
Runnable
實現(xiàn) Runnable
接口優(yōu)于繼承 Thread
類,因為:
- Java 不支持多重繼承,所有的類都只允許繼承一個父類,但可以實現(xiàn)多個接口。如果繼承了
Thread
類就無法繼承其它類,這不利于擴展。 - 類可能只要求可執(zhí)行就行,繼承整個
Thread
類開銷過大。
通過實現(xiàn) Runnable
接口創(chuàng)建線程的步驟:
- 定義
Runnable
接口的實現(xiàn)類,并覆寫該接口的run
方法。該run
方法的方法體同樣是該線程的線程執(zhí)行體。 - 創(chuàng)建
Runnable
實現(xiàn)類的實例,并以此實例作為Thread
的 target 來創(chuàng)建Thread
對象,該Thread
對象才是真正的線程對象。 - 調(diào)用線程對象的
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");// 調(diào)用線程主體tA.start();tB.start();}static class MyThread implements Runnable {private int ticket = 5;@Overridepublic void run() {while (ticket > 0) {System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票");ticket--;}}}}
Callable、Future、FutureTask
繼承 Thread 類和實現(xiàn) Runnable 接口這兩種創(chuàng)建線程的方式都沒有返回值。所以,線程執(zhí)行完后,無法得到執(zhí)行結(jié)果。但如果期望得到執(zhí)行結(jié)果該怎么做?
為了解決這個問題,Java 1.5 后,提供了 Callable
接口和 Future
接口,通過它們,可以在線程執(zhí)行結(jié)束后,返回執(zhí)行結(jié)果。
Callable
Callable 接口只聲明了一個方法,這個方法叫做 call():
public interface Callable<V> {/*** Computes a result, or throws an exception if unable to do so.** @return computed result* @throws Exception if unable to compute a result*/V call() throws Exception;
}
那么怎么使用 Callable 呢?一般情況下是配合 ExecutorService 來使用的,在 ExecutorService 接口中聲明了若干個 submit 方法的重載版本:
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
第一個 submit 方法里面的參數(shù)類型就是 Callable。
Future
Future 就是對于具體的 Callable 任務(wù)的執(zhí)行結(jié)果進行取消、查詢是否完成、獲取結(jié)果。必要時可以通過 get 方法獲取執(zhí)行結(jié)果,該方法會阻塞直到任務(wù)返回結(jié)果。
public interface Future<V> {boolean cancel(boolean mayInterruptIfRunning);boolean isCancelled();boolean isDone();V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}
FutureTask
FutureTask 類實現(xiàn)了 RunnableFuture 接口,RunnableFuture 繼承了 Runnable 接口和 Future 接口。
所以,FutureTask 既可以作為 Runnable 被線程執(zhí)行,又可以作為 Future 得到 Callable 的返回值。
public class FutureTask<V> implements RunnableFuture<V> {// ...public FutureTask(Callable<V> callable) {}public FutureTask(Runnable runnable, V result) {}
}public interface RunnableFuture<V> extends Runnable, Future<V> {void run();
}
事實上,FutureTask 是 Future 接口的一個唯一實現(xiàn)類。
Callable + Future + FutureTask 示例
通過實現(xiàn) Callable
接口創(chuàng)建線程的步驟:
- 創(chuàng)建
Callable
接口的實現(xiàn)類,并實現(xiàn)call
方法。該call
方法將作為線程執(zhí)行體,并且有返回值。 - 創(chuàng)建
Callable
實現(xiàn)類的實例,使用FutureTask
類來包裝Callable
對象,該FutureTask
對象封裝了該Callable
對象的call
方法的返回值。 - 使用
FutureTask
對象作為Thread
對象的 target 創(chuàng)建并啟動新線程。 - 調(diào)用
FutureTask
對象的get
方法來獲得線程執(zhí)行結(jié)束后的返回值。
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("任務(wù)耗時:" + (future.get() / 1000000) + "毫秒");} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}static class MyThread implements Callable<Long> {private int ticket = 10000;@Overridepublic Long call() {long begin = System.nanoTime();while (ticket > 0) {System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票");ticket--;}long end = System.nanoTime();return (end - begin);}}}
線程基本用法
線程(Thread
)基本方法清單:
方法 | 描述 |
---|---|
run | 線程的執(zhí)行實體。 |
start | 線程的啟動方法。 |
currentThread | 返回對當(dāng)前正在執(zhí)行的線程對象的引用。 |
setName | 設(shè)置線程名稱。 |
getName | 獲取線程名稱。 |
setPriority | 設(shè)置線程優(yōu)先級。Java 中的線程優(yōu)先級的范圍是 [1,10],一般來說,高優(yōu)先級的線程在運行時會具有優(yōu)先權(quán)??梢酝ㄟ^ thread.setPriority(Thread.MAX_PRIORITY) 的方式設(shè)置,默認優(yōu)先級為 5。 |
getPriority | 獲取線程優(yōu)先級。 |
setDaemon | 設(shè)置線程為守護線程。 |
isDaemon | 判斷線程是否為守護線程。 |
isAlive | 判斷線程是否啟動。 |
interrupt | 中斷另一個線程的運行狀態(tài)。 |
interrupted | 測試當(dāng)前線程是否已被中斷。通過此方法可以清除線程的中斷狀態(tài)。換句話說,如果要連續(xù)調(diào)用此方法兩次,則第二次調(diào)用將返回 false(除非當(dāng)前線程在第一次調(diào)用清除其中斷狀態(tài)之后且在第二次調(diào)用檢查其狀態(tài)之前再次中斷)。 |
join | 可以使一個線程強制運行,線程強制運行期間,其他線程無法運行,必須等待此線程完成之后才可以繼續(xù)執(zhí)行。 |
Thread.sleep | 靜態(tài)方法。將當(dāng)前正在執(zhí)行的線程休眠。 |
Thread.yield | 靜態(tài)方法。將當(dāng)前正在執(zhí)行的線程暫停,讓其他線程執(zhí)行。 |
線程休眠
使用 Thread.sleep
方法可以使得當(dāng)前正在執(zhí)行的線程進入休眠狀態(tài)。
使用 Thread.sleep
需要向其傳入一個整數(shù)值,這個值表示線程將要休眠的毫秒數(shù)。
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;}@Overridepublic void run() {try {// 休眠指定的時間Thread.sleep(this.time);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(this.name + "休眠" + this.time + "毫秒。");}}}
線程禮讓
Thread.yield
方法的調(diào)用聲明了當(dāng)前線程已經(jīng)完成了生命周期中最重要的部分,可以切換給其它線程來執(zhí)行 。
該方法只是對線程調(diào)度器的一個建議,而且也只是建議具有相同優(yōu)先級的其它線程可以運行。
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 {@Overridepublic 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
停止線程會導(dǎo)致它解鎖所有已鎖定的監(jiān)視器(由于未經(jīng)檢查的ThreadDeath
異常會在堆棧中傳播,這是自然的結(jié)果)。 如果先前由這些監(jiān)視器保護的任何對象處于不一致狀態(tài),則損壞的對象將對其他線程可見,從而可能導(dǎo)致任意行為。stop() 方法會真的殺死線程,不給線程喘息的機會,如果線程持有 ReentrantLock 鎖,被 stop() 的線程并不會自動調(diào)用 ReentrantLock 的 unlock() 去釋放鎖,那其他線程就再也沒機會獲得 ReentrantLock 鎖,這實在是太危險了。所以該方法就不建議使用了,類似的方法還有 suspend() 和 resume() 方法,這兩個方法同樣也都不建議使用了,所以這里也就不多介紹了。
Thread.stop
的許多用法應(yīng)由僅修改某些變量以指示目標線程應(yīng)停止運行的代碼代替。 目標線程應(yīng)定期檢查此變量,如果該變量指示要停止運行,則應(yīng)按有序方式從其運行方法返回。如果目標線程等待很長時間(例如,在條件變量上),則應(yīng)使用中斷方法來中斷等待。
當(dāng)一個線程運行時,另一個線程可以直接通過 interrupt
方法中斷其運行狀態(tài)。
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("3、main線程休眠被終止");}t.interrupt(); // 中斷線程執(zhí)行}static class MyThread implements Runnable {@Overridepublic void run() {System.out.println("1、進入run()方法");try {Thread.sleep(10000); // 線程休眠10秒System.out.println("2、已經(jīng)完成了休眠");} catch (InterruptedException e) {System.out.println("3、MyThread線程休眠被終止");return; // 返回調(diào)用處}System.out.println("4、run()方法正常結(jié)束");}}
}
如果一個線程的 run
方法執(zhí)行一個無限循環(huán),并且沒有執(zhí)行 sleep
等會拋出 InterruptedException
的操作,那么調(diào)用線程的 interrupt
方法就無法使線程提前結(jié)束。
但是調(diào)用 interrupt
方法會設(shè)置線程的中斷標記,此時調(diào)用 interrupted
方法會返回 true
。因此可以在循環(huán)體中使用 interrupted
方法來判斷線程是否處于中斷狀態(tài),從而提前結(jié)束線程。
安全地終止線程有兩種方法:
- 定義
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;@Overridepublic 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;@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " 線程啟動");// 通過 Thread.interrupted 和 interrupt 配合來控制線程終止while (!Thread.interrupted()) {System.out.println(count++);}System.out.println(Thread.currentThread().getName() + " 線程終止");}}
}
守護線程
什么是守護線程?
- 守護線程(Daemon Thread)是在后臺執(zhí)行并且不會阻止 JVM 終止的線程。當(dāng)所有非守護線程結(jié)束時,程序也就終止,同時會殺死所有守護線程。
- 與守護線程(Daemon Thread)相反的,叫用戶線程(User Thread),也就是非守護線程。
為什么需要守護線程?
- 守護線程的優(yōu)先級比較低,用于為系統(tǒng)中的其它對象和線程提供服務(wù)。典型的應(yīng)用就是垃圾回收器。
如何使用守護線程?
- 可以使用
isDaemon
方法判斷線程是否為守護線程。 - 可以使用
setDaemon
方法設(shè)置線程為守護線程。- 正在運行的用戶線程無法設(shè)置為守護線程,所以
setDaemon
必須在thread.start
方法之前設(shè)置,否則會拋出llegalThreadStateException
異常; - 一個守護線程創(chuàng)建的子線程依然是守護線程。
- 不要認為所有的應(yīng)用都可以分配給守護線程來進行服務(wù),比如讀寫操作或者計算邏輯。
- 正在運行的用戶線程無法設(shè)置為守護線程,所以
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 {@Overridepublic void run() {while (true) {System.out.println(Thread.currentThread().getName() + "在運行。");}}}
}
參考閱讀:Java 中守護線程的總結(jié)
線程通信
當(dāng)多個線程可以一起工作去解決某個問題時,如果某些部分必須在其它部分之前完成,那么就需要對線程進行協(xié)調(diào)。
wait/notify/notifyAll
wait
-wait
會自動釋放當(dāng)前線程占有的對象鎖,并請求操作系統(tǒng)掛起當(dāng)前線程,讓線程從Running
狀態(tài)轉(zhuǎn)入Waiting
狀態(tài),等待notify
/notifyAll
來喚醒。如果沒有釋放鎖,那么其它線程就無法進入對象的同步方法或者同步控制塊中,那么就無法執(zhí)行notify
或者notifyAll
來喚醒掛起的線程,造成死鎖。notify
- 喚醒一個正在Waiting
狀態(tài)的線程,并讓它拿到對象鎖,具體喚醒哪一個線程由 JVM 控制 。notifyAll
- 喚醒所有正在Waiting
狀態(tài)的線程,接下來它們需要競爭對象鎖。
注意:
wait
、notify
、notifyAll
都是Object
類中的方法,而非Thread
。wait
、notify
、notifyAll
只能用在synchronized
方法或者synchronized
代碼塊中使用,否則會在運行時拋出IllegalMonitorStateException
。為什么
wait
、notify
、notifyAll
不定義在Thread
中?為什么wait
、notify
、notifyAll
要配合synchronized
使用?首先,需要了解幾個基本知識點:
- 每一個 Java 對象都有一個與之對應(yīng)的 監(jiān)視器(monitor)
- 每一個監(jiān)視器里面都有一個 對象鎖 、一個 等待隊列、一個 同步隊列
了解了以上概念,我們回過頭來理解前面兩個問題。
為什么這幾個方法不定義在
Thread
中?由于每個對象都擁有對象鎖,讓當(dāng)前線程等待某個對象鎖,自然應(yīng)該基于這個對象(
Object
)來操作,而非使用當(dāng)前線程(Thread
)來操作。因為當(dāng)前線程可能會等待多個線程的鎖,如果基于線程(Thread
)來操作,就非常復(fù)雜了。為什么
wait
、notify
、notifyAll
要配合synchronized
使用?如果調(diào)用某個對象的
wait
方法,當(dāng)前線程必須擁有這個對象的對象鎖,因此調(diào)用wait
方法必須在synchronized
方法和synchronized
代碼塊中。
生產(chǎn)者、消費者模式是 wait
、notify
、notifyAll
的一個經(jīng)典使用案例:
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("生產(chǎn)者A").start();new Producer("生產(chǎn)者B").start();new Consumer("消費者A").start();new Consumer("消費者B").start();}static class Consumer extends Thread {Consumer(String name) {super(name);}@Overridepublic void run() {while (true) {synchronized (queue) {while (queue.size() == 0) {try {System.out.println("隊列空,等待數(shù)據(jù)");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() + " 從隊列取走一個元素,隊列當(dāng)前有:" + queue.size() + "個元素");}}}}static class Producer extends Thread {Producer(String name) {super(name);}@Overridepublic 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() + " 向隊列取中插入一個元素,隊列當(dāng)前有:" + queue.size() + "個元素");}}}}
}
join
在線程操作中,可以使用 join
方法讓一個線程強制運行,線程強制運行期間,其他線程無法運行,必須等待此線程完成之后才可以繼續(xù)執(zhí)行。
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 {@Overridepublic void run() {for (int i = 0; i < 50; i++) {System.out.println(Thread.currentThread().getName() + " 運行,i = " + i); // 取得當(dāng)前線程的名字}}}
}
管道
管道輸入/輸出流和普通的文件輸入/輸出流或者網(wǎng)絡(luò)輸入/輸出流不同之處在于,它主要用于線程之間的數(shù)據(jù)傳輸,而傳輸?shù)拿浇闉閮?nèi)存。
管道輸入/輸出流主要包括了如下 4 種具體實現(xiàn):PipedOutputStream
、PipedInputStream
、PipedReader
和 PipedWriter
,前兩種面向字節(jié),而后兩種面向字符。
public class Piped {public static void main(String[] args) throws Exception {PipedWriter out = new PipedWriter();PipedReader in = new PipedReader();// 將輸出流和輸入流進行連接,否則在使用時會拋出IOExceptionout.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 種不同的線程狀態(tài),在給定的一個時刻,線程只能處于其中的一個狀態(tài)。
以下是各狀態(tài)的說明,以及狀態(tài)間的聯(lián)系:
-
新建(New) - 尚未調(diào)用
start
方法的線程處于此狀態(tài)。此狀態(tài)意味著:創(chuàng)建的線程尚未啟動。 -
就緒(Runnable) - 已經(jīng)調(diào)用了
start
方法的線程處于此狀態(tài)。此狀態(tài)意味著:線程已經(jīng)在 JVM 中運行。但是在操作系統(tǒng)層面,它可能處于運行狀態(tài),也可能等待資源調(diào)度(例如處理器資源),資源調(diào)度完成就進入運行狀態(tài)。所以該狀態(tài)的可運行是指可以被運行,具體有沒有運行要看底層操作系統(tǒng)的資源調(diào)度。 -
阻塞(Blocked) - 此狀態(tài)意味著:線程處于被阻塞狀態(tài)。表示線程在等待
synchronized
的隱式鎖(Monitor lock)。synchronized
修飾的方法、代碼塊同一時刻只允許一個線程執(zhí)行,其他線程只能等待,即處于阻塞狀態(tài)。當(dāng)占用synchronized
隱式鎖的線程釋放鎖,并且等待的線程獲得synchronized
隱式鎖時,就又會從BLOCKED
轉(zhuǎn)換到RUNNABLE
狀態(tài)。 -
等待(Waiting) - 此狀態(tài)意味著:線程無限期等待,直到被其他線程顯式地喚醒。 阻塞和等待的區(qū)別在于,阻塞是被動的,它是在等待獲取
synchronized
的隱式鎖。而等待是主動的,通過調(diào)用Object.wait
等方法進入。進入方法 退出方法 沒有設(shè)置 Timeout 參數(shù)的 Object.wait
方法Object.notify
/Object.notifyAll
沒有設(shè)置 Timeout 參數(shù)的 Thread.join
方法被調(diào)用的線程執(zhí)行完畢 LockSupport.park
方法(Java 并發(fā)包中的鎖,都是基于它實現(xiàn)的)LockSupport.unpark
-
定時等待(Timed waiting) - 此狀態(tài)意味著:無需等待其它線程顯式地喚醒,在一定時間之后會被系統(tǒng)自動喚醒。
進入方法 退出方法 Thread.sleep
方法時間結(jié)束 獲得 synchronized
隱式鎖的線程,調(diào)用設(shè)置了 Timeout 參數(shù)的Object.wait
方法時間結(jié)束 / Object.notify
/Object.notifyAll
設(shè)置了 Timeout 參數(shù)的 Thread.join
方法時間結(jié)束 / 被調(diào)用的線程執(zhí)行完畢 LockSupport.parkNanos
方法LockSupport.unpark
LockSupport.parkUntil
方法LockSupport.unpark
-
終止(Terminated) - 線程執(zhí)行完
run
方法,或者因異常退出了run
方法。此狀態(tài)意味著:線程結(jié)束了生命周期。
線程常見問題
sleep、yield、join 方法有什么區(qū)別
yield
方法yield
方法會 讓線程從Running
狀態(tài)轉(zhuǎn)入Runnable
狀態(tài)。- 當(dāng)調(diào)用了
yield
方法后,只有與當(dāng)前線程相同或更高優(yōu)先級的Runnable
狀態(tài)線程才會獲得執(zhí)行的機會。
sleep
方法sleep
方法會 讓線程從Running
狀態(tài)轉(zhuǎn)入Waiting
狀態(tài)。sleep
方法需要指定等待的時間,超過等待時間后,JVM 會將線程從Waiting
狀態(tài)轉(zhuǎn)入Runnable
狀態(tài)。- 當(dāng)調(diào)用了
sleep
方法后,無論什么優(yōu)先級的線程都可以得到執(zhí)行機會。 sleep
方法不會釋放“鎖標志”,也就是說如果有synchronized
同步塊,其他線程仍然不能訪問共享數(shù)據(jù)。
join
join
方法會 讓線程從Running
狀態(tài)轉(zhuǎn)入Waiting
狀態(tài)。- 當(dāng)調(diào)用了
join
方法后,當(dāng)前線程必須等待調(diào)用join
方法的線程結(jié)束后才能繼續(xù)執(zhí)行。
為什么 sleep 和 yield 方法是靜態(tài)的
Thread
類的 sleep
和 yield
方法將處理 Running
狀態(tài)的線程。
所以在其他處于非 Running
狀態(tài)的線程上執(zhí)行這兩個方法是沒有意義的。這就是為什么這些方法是靜態(tài)的。它們可以在當(dāng)前正在執(zhí)行的線程中工作,并避免程序員錯誤的認為可以在其他非運行線程調(diào)用這些方法。
Java 線程是否按照線程優(yōu)先級嚴格執(zhí)行
即使設(shè)置了線程的優(yōu)先級,也無法保證高優(yōu)先級的線程一定先執(zhí)行。
原因在于線程優(yōu)先級依賴于操作系統(tǒng)的支持,然而,不同的操作系統(tǒng)支持的線程優(yōu)先級并不相同,不能很好的和 Java 中線程優(yōu)先級一一對應(yīng)。
一個線程兩次調(diào)用 start()方法會怎樣
Java 的線程是不允許啟動兩次的,第二次調(diào)用必然會拋出 IllegalThreadStateException,這是一種運行時異常,多次調(diào)用 start 被認為是編程錯誤。
start
和 run
方法有什么區(qū)別
run
方法是線程的執(zhí)行體。start
方法會啟動線程,然后 JVM 會讓這個線程去執(zhí)行run
方法。
可以直接調(diào)用 Thread
類的 run
方法么
- 可以。但是如果直接調(diào)用
Thread
的run
方法,它的行為就會和普通的方法一樣。 - 為了在新的線程中執(zhí)行我們的代碼,必須使用
Thread
的start
方法。
參考資料
- 進程和線程關(guān)系及區(qū)別
- sleep(),wait(),yield()和 join()方法的區(qū)別
- Java 并發(fā)編程:線程間協(xié)作的兩種方式:wait、notify、notifyAll 和 Condition
- Java 并發(fā)編程:Callable、Future 和 FutureTask
- Java 中守護線程的總結(jié)
- Java 并發(fā)