多線程基礎
線程進程
進程
每一個正在運行的程序
通過windows 任務管理器 查看正在運行的程序
- 特點
1- 進程是系統運行程序的基本單位
2- 每一個進程都有自己獨立的一塊內存空間,一組系統資源
3- 每一個進程內部的數據和狀態都是完全獨立的
線程
概念
線程是進程執行運算的最小單位, 一個進程在其執行過程中可以產生多個線程,線程必須在某個進程中執行.
線程是進程內部的一個執行單元, 是可完成一個獨立任務的順序控制流程,如果在一個進程中同時運行了多個線程,用來完成不同工作, 稱之爲多線程.
多線程的優勢
迅雷多線程下載
- 最大限度的提高計算機系統的利用效率
- 可以帶來更好的用戶體驗
線程和進程對比
1- 一個進程至少包含一個線程
2- 系統資源分配給進程, 同一進程中的所有線程共享該進程的所有資源
3- cpu 分配給線程執行機會,而不是分配給進程, 真正運行在cpu上的是線程
CPU 採用時間片段
一個CPU 同一時間只能做一件事
時間片段 : 每隔一段時間去執行一個程序, 時間片段使用完畢之後,再切換到另外一個程序, 因爲這個切換速度很快, 用戶感覺不到CPU 正在切換執行程序.
就好比說播放視頻
視頻 幀率 (本質就是一張一張的圖片) 一秒內切換20 張圖片.
連貫的執行圖片切換,讓用戶感覺是在看視頻.
用戶感受不到時間粒度太細的操作
CPU 隨機選擇 一個線程去執行, 感覺上是多個線程是在爭搶CPU 的執行權
線程的使用
在學習本章節之前, 所有的代碼程序都是單線程的
無論代碼有多複雜, 都是按照順序依次執行的
main 函數線程
書寫代碼, 拋出異常
Exception in thread "main" java.lang.NullPointerException
- 主線程入口
- 開啓其它子線程
- 最後執行各種關閉資源的操作
Thread
第一種創建線程的方法
1- 將一個類聲明爲Thread的子類。
2- 這個子類應該重寫Thread類的run方法 。
3- 然後可以分配並啓動子類的實例。
創建線程的步驟
1- 書寫子類繼承Thread 類
2- 重寫 run 方法
3- 創建自定義的線程子類對象
4- 調用start() 方法
案例測試
public class MyThread extends Thread{
@Override
public void run() {
for(int i = 0 ; i < 100 ; i++){
System.out.println("Mythread--"+i);
}
}
}
public class Demo2_Thread {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
for(int i = 0 ; i < 100 ; i++){
System.out.println("main--"+i);
}
}
}
start()
1- 啓動一條線程
2- 執行該線程的run()方法
run() 和 start() 區別
run 僅僅是 線程類中的一個普通方法
如果使用線程對象調用run方法, 僅僅是在main 線程中 執行run() 方法中的代碼
start() 纔可以真正的啓動線程
start() 可以自動去執行run() 方法
細節
多個線程執行, 不是真正的並行
線程每次執行的時長是由CPU 隨機分配的時間片決定的
Thread常用方法
優先級
優先級高 有更高的機率可以獲取CPU的更多時間片段, 不能保證一定優先執行
類似於擲骰子 1-4 張三 贏 56 是 李四贏, 真正玩遊戲, 不能保證是張三
int getPriority()
返回此線程的優先級。
setPriority(int newPriority)
更改此線程的優先級。
設置線程名稱
1- 有參構造 Thread(String name)
分配一個新的 Thread對象。
2- void setName(String name)
將此線程的名稱更改爲等於參數 name 。
String getName()
返回此線程的名稱。
案例
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
// 子類如何調用執行 父類的構造方法
super(name);
}
}
new MyThread("線程1");
獲取當前正在執行的線程
static Thread currentThread()
返回對當前正在執行的線程對象的引用。
獲取線程狀態
Thread.State getState()
返回此線程的狀態。
判斷線程是否處於活躍狀態
boolean isAlive()
測試這個線程是否活着。
線程休眠
static void sleep(long millis)
使當前正在執行的線程以指定的毫秒數暫停(暫時停止執行),具體取決於系統定時器和調度程序的精度和準確性。
public void run() {
for(int i = 0 ; i < 100 ; i++){
// 獲取當前線程名稱
String sname = Thread.currentThread().getName();
if(i == 61){
// run 方法 是 子類重寫的父類的方法, 子類拋出的異常不能超過父類的異常
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(sname+"--"+i);
}
線程加入
void join()
等待這個線程死亡。
ThreadTest tt1 = new ThreadTest();
ThreadTest tt2 = new ThreadTest();
ThreadTest tt3 = new ThreadTest();
tt1.setName("西施");
tt2.setName("貂蟬");
tt3.setName("王昭君");
tt1.start();
/**join的意思是使得放棄當前線程的執行,並返回對應的線程,例如下面代碼的意思就是:
程序在main線程中調用tt1線程的join方法,則main線程放棄cpu控制權,並返回tt1線程繼續執行直到線程tt1執行完畢
所以結果是tt1線程執行完後,纔到主線程執行,相當於在main線程中同步tt1線程,tt1執行完了,main線程纔有執行的機會
*/
tt1.join();
tt2.start();
// 我要插隊, 等我運行結束之後, 之後的代碼纔可以開始執行
tt3.start();
線程禮讓
static void yield()
對調度程序的一個暗示,即當前線程放棄當前使用的處理器。
public void run() {
for(int i = 0 ; i < 100 ; i++){
// 獲取當前線程名稱
String sname = Thread.currentThread().getName();
System.out.println(sname+"--"+i);
// 當線程運行到該位置 , 放棄當前cpu的執行權
// 但是CPU 依然有可能還選擇你 不能保證均勻
Thread.yield();
}
}
線程終止
void interrupt()
中斷這個線程。
// 詳解
interrupt() 它基於「一個線程不應該由其他線程來強制中斷或停止,而是應該由線程自己自行停止。」思想,是一個比較溫柔的做法,它更類似一個標誌位。其實作用不是中斷線程,而是「通知線程應該中斷了」,具體到底中斷還是繼續運行,應該由被通知的線程自己處理。
interrupt() 並不能真正的中斷線程,這點要謹記。需要被調用的線程自己進行配合才行。也就是說,一個線程如果有被中斷的需求,那麼就需要這樣做:
在正常運行任務時,經常檢查本線程的中斷標誌位,如果被設置了中斷標誌就自行停止線程。
在調用阻塞方法時正確處理InterruptedException異常。(例如:catch異常後就結束線程。)
1- interrupt 中斷操作時,非自身打斷需要先檢測是否有中斷權限,這由jvm的安全機制配置;
如果線程處於sleep, wait, join 等狀態,那麼線程將立即退出被阻塞狀態,並拋出一個InterruptedException異常;
2- 如果線程處於I/O阻塞狀態,將會拋出ClosedByInterruptException(IOException的子類)異常;
3- 如果線程在Selector上被阻塞,select方法將立即返回;
4- 如果非以上情況,將直接標記 interrupt 狀態;
5- 注意:interrupt 操作不會打斷所有阻塞,只有上述阻塞情況纔在jvm的打斷範圍內,如處於鎖阻塞的線程,不會受 interrupt 中斷;
守護線程
public final void setDaemon(boolean on)
將此線程標記爲daemon線程或用戶線程。
當運行的唯一線程都是守護進程線程時,Java虛擬機將退出。
線程啓動前必須調用此方法。
參數 如果 true ,將此線程標記爲守護線程
public static void main(String[] args) {
MyThread t1 = new MyThread("皇帝");
MyThread t2 = new MyThread("總管1");
MyThread t3 = new MyThread("總管2");
// 線程啓動之前設置 守護線程
t2.setDaemon(true);
t3.setDaemon(true);
// 只要主線程運行完畢, 守護線程 不是立即停止, 等待虛擬機退出
t1.start();
t2.start();
t3.start();
}
Runnable 接口 創建線程
第二種創建線程的方法
查看API Thread 的構造方法
Thread(Runnable target)
分配一個新的 Thread對象。
Thread(Runnable target, String name)
分配一個新的 Thread對象。
// 如何啓動一個 Runnable 接口的線程
MyRunnable mr = new MyRunnable();
// 創建線程離不開 Thread
Thread t1 = new Thread(mr,"線程名稱1");
Thread t2 = new Thread(mr,"線程名稱2");
t1.start();
t2.start();
使用類創建線程 使用接口創建線程的區別
回憶 類和接口的區別
1- 類只能單繼承 接口可以多實現
2- 類是什麼是什麼的概念 接口 什麼有什麼功能
自己實現一個線程 如果採用繼承的方式 , 只能單繼承, 不能夠再繼承其他類
多線程也可以看做是一種功能, 可以實現 業務和邏輯的分離
推薦使用接口來創建線程
線程狀態
線程的生命週期
// 如何啓動一個 runnable 接口的線程
MyRunnable mr = new MyRunnable();
// 創建線程離不開 Thread
Thread t1 = new Thread(mr,"線程名稱1");
Thread t2 = new Thread(mr,"線程名稱2");
t1.start();
t2.start();
線程狀態圖例
線程的5種狀態
1- 新建 new 對象
2- 就緒 可運行狀態 (對象調用了 start() 方法之後)
3- 運行狀態 (CPU 選擇了 線程)
4- 死亡 run方法運行完畢
5- 阻塞 由運行狀態==> 阻塞狀態