01、簡介
百丈高樓平地起,要想學好多線程,首先還是的了解一下線程的基礎,這邊文章將帶著大家來了解一下線程的基礎知識。
02、線程的創建方式
- 實現 Runnable 接口
- 繼承 Thread 類
- 實現 Callable 接口通過 FutureTask 包裝器來創建線程
- 通過線程池創建線程
下面將用線程池和 “Callable 的方式來創建線程
public class CallableDemo implements Callable<String> {
@Override public String call() throws Exception { int a=1; int b=2; System. out .println(a+b); return “執行結果:”+(a+b); }
public static void main(String[] args) throws ExecutionException, InterruptedException { //創建一個可重用固定線程數爲1的線程池 ExecutorService executorService = Executors.newFixedThreadPool (1); CallableDemo callableDemo=new CallableDemo(); //執行線程,用future來接收線程的返回值 Future<String> future = executorService.submit(callableDemo); //打印線程的返回值 System. out .println(future.get()); executorService.shutdown(); } }
執行結果
3 執行結果:3
03、線程的生命周期
- NEW:初始狀態,線程被構建,但是還沒有調用 start 方法。
- RUNNABLED:運行狀態,JAVA 線程把操作系統中的就緒和運行兩種狀態統一稱爲“運行中”。調用線程的 start() 方法使線程進入就緒狀態。
- BLOCKED:阻塞狀態,表示線程進入等待狀態,也就是線程因爲某種原因放棄了 CPU 使用權。比如訪問 synchronized 關鍵字修飾的方法,沒有獲得對象鎖。
- Waiting :等待狀態,比如調用wait()方法。
- TIME_WAITING:超時等待狀態,超時以後自動返回。比如調用 sleep(long millis) 方法
- TERMINATED:終止狀態,表示當前線程執行完畢。
看下源碼:
public enum State { NEW,
RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
04、線程的優先級
- 線程的最小優先級:1
- 線程的最大優先級:10
- 線程的默認優先級:5
- 通過調用 getPriority() 和 setPriority(int newPriority) 方法來獲得和設置線程的優先級
看下源碼:
/** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1;
/** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5;
/** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10;
看下代碼:
public class ThreadA extends Thread {
public static void main(String[] args) { ThreadA a = new ThreadA(); System.out.println(a.getPriority());//5 a.setPriority(8); System.out.println(a.getPriority());//8 } } 線程優先級特性:
- 繼承性:比如A線程啓動B線程,則B線程的優先級與A是一樣的。
- 規則性:高優先級的線程總是大部分先執行完,但不代表高優先級線程全部先執行完。
- 隨機性:優先級較高的線程不一定每一次都先執行完。
05、線程的停止
- stop() 方法,這個方法已經標記爲過時了,強制停止線程,相當于kill -9。
- interrupt() 方法,優雅的停止線程。告訴線程可以停止了,至于線程什麽時候停止,取決于線程自身。
看下停止線程的代碼:
public class InterruptDemo { private static int i ; public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { //默認情況下isInterrupted 返回 false、通過 thread.interrupt 變成了 true while (!Thread.currentThread().isInterrupted()) { i++; } System.out.println(“Num:” + i); }, “interruptDemo”); thread.start(); TimeUnit.SECONDS.sleep(1); thread.interrupt(); //不加這句,thread線程不會停止 } }
看上面這段代碼,主線程main方法調用 thread線程的 interrupt() 方法,就是告訴 thread 線程,你可以停止了(其實是將 thread 線程的一個屬性設置爲了true),然後 thread 線程通過 isInterrupted() 方法獲取這個屬性來判斷是否設置爲了true。這裏我再舉一個例子來說明一下,
看代碼:
public class ThreadDemo { private volatile static Boolean interrupt = false ; private static int i ;
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { while (!interrupt) { i++; } System.out.println(“Num:” + i); }, “ThreadDemo”); thread.start(); TimeUnit.SECONDS.sleep(1); interrupt = true; } }
是不是很相似,再簡單總結一下:
當其他線程通過調用當前線程的 interrupt 方法,表示向當前線程打個招呼,告訴他可以中斷線程的執行了,並不會立即中斷線程,至于什麽時候中斷,取決于當前線程自己。
線程通過檢查資深是否被中斷來進行相應,可以通過 isInterrupted() 來判斷是否被中斷。
這種通過標識符來實現中斷操作的方式能夠使線程在終止時有機會去清理資源,而不是武斷地將線程停止,因此這種終止線程的做法顯得更加安全和優雅。
06、線程的複位
兩種複位方式:
- Thread.interrupted()
- 通過抛出 InterruptedException 的方式
然後了解一下什麽是複位:
線程運行狀態時 Thread.isInterrupted() 返回的線程狀態是 false,然後調用 thread.interrupt() 中斷線程 Thread.isInterrupted() 返回的線程狀態是 true,最後調用 Thread.interrupted() 複位線程Thread.isInterrupted() 返回的線程狀態是 false 或者抛出 InterruptedException 異常之前,線程會將狀態設爲 false。
下面來看下兩種方式複位線程的代碼,首先是 Thread.interrupted() 的方式複位代碼:
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { while (true) { //Thread.currentThread().isInterrupted()默認是false,當main方式執行thread.interrupt()時,狀態改爲true if (Thread.currentThread().isInterrupted()) { System.out.println(“before:” + Thread.currentThread().isInterrupted());//before:true Thread.interrupted(); // 對線程進行複位,由 true 變成 false System.out.println(“after:” + Thread.currentThread().isInterrupted());//after:false } } }, “interruptDemo”); thread.start(); TimeUnit.SECONDS.sleep(1); thread.interrupt(); } }
抛出 InterruptedException 複位線程代碼:
public class InterruptedExceptionDemo {
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); // break; } } }, “interruptDemo”); thread.start(); TimeUnit.SECONDS.sleep(1); thread.interrupt(); System.out.println(thread.isInterrupted()); } }
結果:
false java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at java.lang.Thread.sleep(Thread.java:340) at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386) at com.cl.concurrentprogram.InterruptedExceptionDemo.lambda$main$0(InterruptedExceptionDemo.java:16) at java.lang.Thread.run(Thread.java:748)
需要注意的是,InterruptedException 異常的抛出並不意味著線程必須終止,而是提醒當前線程有中斷的操作發生,至于接下來怎麽處理取決于線程本身,比如
- 直接捕獲異常不做任何處理
- 將異常往外抛出
- 停止當前線程,並打印異常信息
像我上面的例子,如果抛出 InterruptedException 異常,我就break跳出循環讓 thread 線程終止。
爲什麽要複位:
Thread.interrupted()是屬于當前線程的,是當前線程對外界中斷信號的一個響應,表示自己已經得到了中斷信號,但不會立刻中斷自己,具體什麽時候中斷由自己決定,讓外界知道在自身中斷前,他的中斷狀態仍然是 false,這就是複位的原因。