1. 線程概述 1.1 線程和進程 進程是處於運行過程中的程式,並且具有一定的獨立功能 併發性:同一個時刻只能有一條指令執行,但多個進程指令被快速輪換執行 並行:多條指令在多個處理器上同時執行 線程是進程的執行單元 1.2 多線程的優勢 進程之間不能共用記憶體,但線程之間非常容易 系統創建進程時需要為 ...
1.1 線程和進程
-
進程是處於運行過程中的程式,並且具有一定的獨立功能
-
併發性:同一個時刻只能有一條指令執行,但多個進程指令被快速輪換執行
-
並行:多條指令在多個處理器上同時執行
-
線程是進程的執行單元
1.2 多線程的優勢
-
進程之間不能共用記憶體,但線程之間非常容易
-
系統創建進程時需要為該進程重新分配系統資源,但創建線程則代價小得多,因此使用多線程效率更高
-
Java語言內置了多線程功能
2. 線程創建與啟動
2.1 繼承Thread
public class FirstThread extends Thread { private int i; @Override public void run() { for(i = 0; i < 50; i ++){ System.out.println(this.getName() + "" + i); } } public static void main(String[] args){ FirstThread ft = new FirstThread(); for(int i =0; i < 100;i ++){ System.out.println(Thread.currentThread().getName() + "" + i); if(i == 20) { ft.run(); } } } }
2.2 實現Runnable介面
public class FirstThread implements java.lang.Runnable { private int i; public void run() { for(i = 0; i < 50; i ++){ System.out.println(Thread.currentThread().getName()+ "" + i); } } public static void main(String[] args){ FirstThread ft = new FirstThread(); for(int i =0; i < 100;i ++){ System.out.println(Thread.currentThread().getName() + "" + i); if(i == 20) { ft.run(); } } } }
2.3 使用Callable和Future
-
Callable
介面提供了一個call()
方法可以作為線程執行體,call()
方法有返回值且可以聲明拋出異常 -
Java5提供了
Future
介面來代表Callable
介面里call()
方法的返回值,併為Future
介面提供了一個FutureTask
實現類 -
Future
介面定義的方法:
方法名 | 作用 |
---|---|
boolean cancel(boolean mayInterruptIfRunning) |
試圖取消該Future 里關聯的Callable 任務 |
V get() |
返回Callable 任務里call 方法的返回值,該方法會造成線程阻塞,等子線程執行完才能獲得 |
V get(long timeout, TimeUnit unit) |
返回Callable 任務里call 方法的返回值。該方法讓程式最多阻塞timeout 和unit 指定的時間,如果經過指定時間Callable 任務還沒有返回值則拋出TimeoutException 異常 |
boolean isCancelled() |
Callable 中的任務是否取消 |
boolean isDone() |
Callable 中的任務是否完成 |
public class CallableDemo { public static void main(String[] args){ FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> { int i = 0; for( ; i < 100; i++){ System.out.println(i); } return i; }); new Thread(task).start(); try { System.out.println(task.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
2.4 創建線程的三種方式對比
Runnable
和Callable
優劣勢:
-
線程類只是實現了
Runnable
、Callable
介面,還可以繼承其他類 -
Runnable和Callable情況下,多個線程可以共用同一個
target
對象,所以非常適合多個相同線程來處理同一份資源的情況 -
編程稍稍複雜,如果需要訪問當前線程,則必須使用
Thread.currentThread()
Thread
優劣勢:
-
線程類已經繼承了
Thread
類,所以不能再繼承其他父類 -
編寫簡單,如果需要訪問當前線程,用
this
使用
3. 線程生命周期
3.1 新建和就緒狀態
-
new
語句僅僅由Java虛擬機為其分配記憶體,並沒有表現出任何線程的動態特征 -
如果直接調用繼承類的
run
方法,則只會有MainActivity
,而且不能通過getName
獲得當前執行線程的名字,而需用Thread.currentThread().getName()
-
調用了
run
方法後,該線程已經不再處於新建狀態
3.2 運行和阻塞狀態
-
當線程數大於處理器數時,存在多個線程在同一個CPU上輪換的現象
-
協作式調度策略:只有當一個線程調用了
sleep()
或yield()
方法才會放棄所占用的資源——即必須線程主動放棄所占用的資源 -
搶占式調度策略:系統給每個可執行的線程分配一個小的時間段來處理任務,當任務完成後,系統會剝奪該線程所占用的資源
-
被阻塞的線程會在合適的時候重新進入就緒狀態
線程狀態轉換圖
3.3 死亡狀態
-
測試線程死亡可用
isAlive()
-
處於死亡的線程無法再次運行,否則引發
IllegalThreadStateException
異常
“大清亡於閉關鎖國,學習技術需要交流和資料”。 在這裡我給大家準備了很多的學習資料免費獲取,包括但不限於java進階學習資料、技術乾貨、大廠面試題系列、技術動向、職業生涯等一切有關程式員的分享.java進階方法筆記,學習資料,面試題,電子書籍免費領取,讓你成為java大神,追到自己的女神,走向人生巔峰
4. 控制線程
4.1 join線程
-
在
MainActivity
調用了A.join()
,則MainActivity
被阻塞,A線程執行完後MainActivity
才執行
4.2 後臺線程(Daemon Thread)
-
如果所有的前臺線程都死亡,後臺線程會自動死亡
運行結果
4.3 線程睡眠sleep
try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
-
sleep
方法暫停當前線程後,會給其他線程執行機會,不會理會其他線程優先順序;但yield
方法只會給優先順序相同或更高的線程 -
sleep
方法將轉入阻塞狀態,直到經過阻塞時間才會轉入就緒;yield
強制當前線程轉入就緒狀態 -
sleep
方法拋出了InterruptedException
,yield方法沒拋出異常
4.4 改變線程優先順序
-
優先順序高的線程獲得較多的執行機會,優先順序低的線程獲得較少的執行機會
-
setPriority
和getPriority
方法來設置和返回指定線程的優先順序
5. 線程同步
-
run()
方法不具有同步安全性 -
*java*引入了同步監視器來解決多線程同步問題,
sychronized(obj)
中obj
就是共用資源
5.1 同步方法
-
同步方法就是使用
synchronized
來修飾某個方法 -
實例方法的同步監視器預設是
this
-
*Java*中不可變類總是線程安全的,可變類對象需要額外的方法來保證其線程安全
public class DaemonThread extends Thread { static int balance = 100; int drawAmount; String name; public DaemonThread(int drawAmount, String name){ this.drawAmount = drawAmount; this.name = name; } @Override public void run() { this.draw(drawAmount); } public synchronized void draw(int amount){ if(balance >= amount){ System.out.println(this.name + "取出了" + amount); try{ Thread.sleep(1); } catch (InterruptedException e){ e.printStackTrace(); } balance -= amount; System.out.println("\t餘額為" + balance); } else{ System.out.println(this.name + "取現失敗"); } } public static void main(String[] args){ new DaemonThread(50, "A").start(); new DaemonThread(100, "B").start(); } }
5.2 釋放同步監視器的鎖定
下列情況下,線程會釋放對同步監視器的鎖定
-
當前線程的同步方法、同步代碼塊執行結束
-
遇到了break、return
-
-
程式執行了同步監視器對象的
wait()
方法
下列情況下不會釋放:
-
執行同步方法時,程式調用
Thread.sleep()
Thread.yield()
方法 -
其他線程調用了該線程的
suspend
方法
5.3 同步鎖
-
*Java5*開始,提供了一種功能更強大的同步鎖機制,可以通過顯式定義同步鎖對象來實現同步
-
Lock提供了比synchronized更廣泛的鎖定操作,並且支持多個相關的Condition對象
-
Lock類型: Lock ReadWriteLock ReentrantLock:常用,可以對一個加鎖的對象重新加鎖 ReentrantReadWriteLock StampedLock
方法名 | 作用 |
---|---|
lock |
加鎖 |
unlock |
解鎖 |
5.4 死鎖
A等B,B等A
5.5 線程通信
5.5.1 傳統的線程通信
方法名 | 作用 |
---|---|
wait |
導致當前線程等待,直到其他線程調用該同步監視器的notify() 或notifyAll() 方法 |
notify |
喚醒在此同步監視器等待的單個線程 |
notifyAll |
喚醒在此同步監視器等待的所有線程 |
-
wait()
必須在加鎖的情況下執行
5.5.2 使用Condition
-
如果系統中不適用synchronized來保證線程同步,而使用Lock對象來保證同步,那麼無法使用
wait
,notify
,notifyAll()
來進行線程通信 -
當使用
Lock
對象,*Java*提供Condition
保證線程協調 -
Condition
方法如下
方法名 | 作用 |
---|---|
await |
導致當前線程等待,直到其他線程調用該同步監視器的signal() 或signalAll() 方法 |
signal |
喚醒在此Lock對象的單個線程 |
signalAll |
喚醒在此Lock對象的所有線程 |
5.5.3 使用阻塞隊列
-
*Java*提供了一個BlockingQueue介面
-
當生產者線程試圖向
BlockingQueue
放入元素時,如果該隊列已滿,則該線程被阻塞;當消費者線程試圖從BlockingQueue
取出元素時,如果該隊列已空,則該線程被阻塞
方法名 | 作用 |
---|---|
put(E e) |
嘗試把E元素放入BlockingQueue |
take() |
嘗試從BlockingQueue 的頭部取出元素 |
public class BlockingQueueThread extends Thread { private BlockingQueue<String> bq; public BlockingQueueThread(BlockingQueue<String> bq){ this.bq = bq; } @Override public void run() { String[] strColl = new String[]{ "Java", "Kotlin", "JavaScript" }; for(int i = 0; i < 1000; i ++){ try { System.out.println(getName() + "開始動工" + i); bq.put(strColl[i % 3]); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(getName() + "工作結束"); } public static void main(String[] args){ BlockingQueue<String> bq = new ArrayBlockingQueue<>(5); new BlockingQueueThread(bq).start(); } }
結果展示
可以看到,當Thread-0運行到第6次時就已經被阻塞,不能往裡添加內容
“大清亡於閉關鎖國,學習技術需要交流和資料”。 在這裡我給大家準備了很多的學習資料免費獲取,包括但不限於java進階學習資料、技術乾貨、大廠面試題系列、技術動向、職業生涯等一切有關程式員的分享.java進階方法筆記,學習資料,面試題,電子書籍免費領取,讓你成為java大神,追到自己的女神,走向人生巔峰
6. 線程組和未處理的異常
-
ThreadGroup
表示線程組,可以對一批線程進行分類管理 -
子線程和創建它的父線程在同一個線程組內
-
ThreadGroup
方法
方法名 | 作用 |
---|---|
int activeCount |
返回線程組中活動線程的數目 |
interrupt |
中斷此線程組中所有活動線程的數目 |
isDaemon |
線程組是否是後臺線程組 |
setDaemon |
設置後臺線程 |
setMaxPriority |
設置線程組的最高優先順序 |
7. 線程池
-
線程池在系統啟動時即創建大量空閑的線程
-
程式將一個
Runnable
對象或Callable
對象傳給線程池,線程池就會啟動一個空閑線程來執行他們 -
線程結束不死亡,而是回到空閑狀態
-
*Java8*之後新增了一個
Executors
工廠類來生產線程池
7.1 ThreadPool
public class ThreadPoolTest { public static void main(String[] args){ ExecutorService pool = Executors.newFixedThreadPool(2); java.lang.Runnable target = () -> { for (int i = 0; i < 100 ; i ++){ System.out.println(Thread.currentThread().getName() + "的i為" +i); } }; pool.submit(target); pool.submit(target); pool.shutdown(); } }
結果展示
7.2 ForkJoinPool
-
將一個任務拆分成多個小任務並行計算,再把多個小任務的結果合併成總的計算結果
-
ForkJoinPool
是ExecutorService
的實現類
public class PrintTask extends RecursiveAction { public static int THREADSH_HOLD = 50; private int start; private int end; public PrintTask(int start, int end){ this.start = start; this.end = end; } @Override protected void compute() { if(end - start < THREADSH_HOLD){ for(int i = start; i < end; i ++){ System.out.println(Thread.currentThread().getName() + "的i為" + i); } } else { PrintTask left = new PrintTask(start, (start + end) / 2); PrintTask right = new PrintTask((start + end) / 2 , end); left.fork(); right.fork(); } } public static void main(String[] args) throws InterruptedException { PrintTask printTask = new PrintTask(0 , 300); ForkJoinPool pool = new ForkJoinPool(); pool.submit(printTask); pool.awaitTermination(2, TimeUnit.SECONDS); pool.shutdown(); } }