1.單執行緒
單執行緒:只有一個執行緒,即CPU只執行一個任務(一個執行緒)
1 class Hero{ 2 String name; 3 Hero(String name){ 4 this.name = name; 5 } 6 public void show(){ 7 System.out.println(name + ",,,,,"); 8 } 9 } 10 11 public class ThreadDemo { 12 public static void main(String[] args) { 13 Hero d1 = new Hero("亞瑟"); 14 Hero d2 = new Hero("妲己"); 15 Hero d3 = new Hero("貂蟬"); 16 d1.show(); 17 d2.show(); 18 d3.show(); 19 } 20 }
結果:
,多執行緒順序可能會不一樣
2.多執行緒
多執行緒:就是多個執行緒同時運行,一個CPU執行多個任務(執行緒)
- 優點:能讓代碼同時執行,可以大大提高效率
- 能讓代碼同時執行,可以大大提高效率
1.主執行緒
java中,main方法是程式的入口,所以 main 方法被稱為:主方法
- 執行 main 方法中代碼的執行緒,被稱為:主執行緒
需要注意的地方有 2 點:
- main 方法中的代碼都是有 main 執行緒執行的

- 在 main 方法中呼叫其他方法,那么其他方法中的代碼也是由main執行緒執行

2.創建執行緒
除了主執行緒外,java允許我們自己創建執行緒,通常有 2 種方式:
- 繼承Thread類
- 實作Runnable介面
1.Thread類
java中有一個專門描述執行緒的類:Thread,通過繼承這個類可以創建自己的執行緒,比如:
1 //1. 繼承Thread 2 class Hero extends Thread{ 3 String name; 4 Hero(String name){ 5 this.name = name; 6 } 7 //2. 復寫run方法 8 @Override 9 public void run(){ 10 System.out.println(name + ",,,,,"); 11 } 12 } 13 14 public class ThreadDemo { 15 public static void main(String[] args) { 16 //3. 創建執行緒物件 17 Hero yase = new Hero("亞瑟"); 18 Hero daji = new Hero("妲己"); 19 Hero diaochao = new Hero("貂蟬"); 20 //4. 呼叫start方法:啟動執行緒,之后會自動執行 run 方法 21 yase.start(); 22 daji.start(); 23 diaochao.start(); 24 } 25 }
結果:
注意:想要啟動一個執行緒,必須呼叫的是 start 方法,只是呼叫 run 方法并不會開啟新的執行緒
啟動執行緒方法start()和run()區別
在Java中啟動執行緒有兩種方式:呼叫start()方法和呼叫run()方法,它們的區別如下:
- 呼叫start()方法:會啟動一個新執行緒,并在新執行緒中執行run()方法里面的代碼,
- 呼叫run()方法:不會啟動新執行緒,而是在當前執行緒中同步執行run()方法里面的代碼,
- 只有呼叫start()方法,才會表現出多執行緒的特性,不同執行緒的run()方法里面的代碼交替執行,如果只是呼叫run()方法,那么代碼還是同步執行的,必須等待一個執行緒的run()方法里面的代碼全部執行完畢之后,另外一個執行緒才可以執行其run()方法里面的代碼,
2.實作Runnable介面
實作 java 中的 Runnable 介面并復寫 run 方法,也能創建執行緒,比如:
1 //1. 實作Runnable介面 2 class Hero implements Runnable{ 3 String name; 4 Hero(String name){ 5 this.name = name; 6 } 7 //2. 實作run方法 8 @Override 9 public void run() { 10 System.out.println(name + ",,,,,"); 11 } 12 } 13 public class ThreadDemo { 14 public static void main(String[] args) { 15 //3. 創建Thread物件時把Runnable物件作為引數扔進去 16 Thread t1 = new Thread(new Hero("亞瑟")); 17 Thread t2 = new Thread(new Hero("妲己")); 18 Thread t3 = new Thread(new Hero("貂蟬")); 19 //4.呼叫start方法,啟動一個執行緒,會自動呼叫run方法 20 t1.start(); 21 t2.start(); 22 t3.start(); 23 } 24 }
結果:
通常在學習時,我們都用 ‘匿名內部類’創建執行緒,比如:
1 public static void main(String[] args) { 2 //使用 匿名內部類 創建執行緒 3 Thread yase = new Thread(new Runnable() { 4 @Override 5 public void run() { 6 System.out.println("使用匿名內部類創建一個執行緒,,,,,,"); 7 } 8 },"yase"); 9 yase.start(); 10 }
實作 Runnable 介面和繼承 Thread 比較:
- java不支持多繼承,如果繼承Thread,那么就不能繼承其他類了
- java支持多實作,如果實作Runnable,不但可以實作其他介面,也可以繼承其他類
作業中推薦使用 Runnable 的方式創建執行緒
3.執行緒名
為了區分不同的執行緒,默認情況下每個執行緒都有名稱
1.獲取執行緒名
通過呼叫 getName() 方法獲取

結果:
2.主執行緒名字
主執行緒也是有名稱的,但是我們無法使用 this.getName() 獲取主執行緒的名稱
- 想要獲取主執行緒名稱,得先獲取主執行緒物件
Thread類中有一個靜態方法:currentThread(),用來獲取當前執行緒物件,比如:

結果:
推薦使用:Thread.currentThread().getName() 獲取執行緒名稱
3.設定執行緒名
如果對默認的名稱不滿意,也可以在創建物件的時候自定義執行緒名稱
- 繼承 Thread 類
代碼:
1 class Hero extends Thread{ 2 String name; 3 //1. 增加一個 threadName 引數,作為執行緒名 4 Hero(String name,String threadName){ 5 //2. 呼叫super,把自定義名稱扔給父類 6 super(threadName); 7 this.name = name; 8 } 9 @Override 10 public void run(){ 11 //3. 通過Thread.currentThread().getName()獲取執行緒名稱 12 System.out.println(name + ",,,,,"+Thread.currentThread().getName()); 13 } 14 } 15 16 public class ThreadDemo { 17 public static void main(String[] args) { 18 //4. 創建Thread實體物件,自定義執行緒名 19 Hero d1 = new Hero("亞瑟", "心靈戰士"); 20 Hero d2 = new Hero("妲己","女仆咖啡"); 21 Hero d3 = new Hero("貂蟬", "異域舞娘"); 22 d1.start(); 23 d2.start(); 24 d3.start(); 25 } 26 }
結果:
2.實作Runnable介面
1 class Hero implements Runnable{ 2 String name; 3 Hero(String name){ 4 this.name = name; 5 } 6 @Override 7 public void run() { 8 System.out.println(name + ",,,,,"+Thread.currentThread().getName()); 9 } 10 } 11 public class ThreadDemo { 12 public static void main(String[] args) { 13 //創建Thread物件時候,直接把執行緒名稱扔進去 14 Thread t1 = new Thread(new Hero("亞瑟"), "心靈戰士"); 15 Thread t2 = new Thread(new Hero("妲己"), "女仆咖啡"); 16 Thread t3 = new Thread(new Hero("貂蟬"), "異域舞娘"); 17 t1.start(); 18 t2.start(); 19 t3.start(); 20 } 21 }
結果:
4.執行緒名好處
執行緒名在實際作業中很重要,有以下好處
- 除錯方便:當程式運行出現問題時,如果每個執行緒都有自己的名稱,可以快速定位問題
- 可讀性提高:如果代碼中存在多個執行緒,如果有名稱可以使得代碼更易于理解和維護,
- 日志記錄方便:當出現問題時,如果執行緒有名稱,根據日志可以更方便地識別每個執行緒的相關資訊
例:
1 class Hero implements Runnable{ 2 String name; 3 Hero(String name){ 4 this.name = name; 5 } 6 @Override 7 public void run() { 8 //如果 name 為null,會報例外 9 if(this.name.length()>0){ 10 System.out.println("aaaaaaaaaaaa"); 11 } 12 System.out.println(name + ",,,,,"); 13 } 14 } 15 public class Demo { 16 public static void main(String[] args) { 17 Thread t1 = new Thread(new Hero(null)); 18 Thread t2 = new Thread(new Hero("妲己")); 19 Thread t3 = new Thread(new Hero("貂蟬")); 20 //4.呼叫start方法,啟動一個執行緒,會自動呼叫run方法 21 t1.start(); 22 t2.start(); 23 t3.start(); 24 25 } 26 }
上面代碼中沒有設定執行緒名,結果:

上圖中雖然有默認的執行緒名,但是根據 Thread-0 很難定位到 Thread t1 = new Thread(new Hero(null)); 這句代碼在什么地方
修改代碼,設定執行緒名:
1 Thread t1 = new Thread(new Hero("亞瑟"), "心靈戰士"); 2 Thread t2 = new Thread(new Hero("妲己"), "女仆咖啡"); 3 Thread t3 = new Thread(new Hero("貂蟬"), "異域舞娘");
結果:
4.多執行緒提高作業效率
示例:醫生給病人看病,一個人需要10分鐘,兩個人就需要20分鐘,如果兩個醫生,那么一共需要10分鐘
示例:
1 class SickPerson{ 2 String name; 3 4 public SickPerson(String name){ 5 this.name = name; 6 } 7 } 8 9 public class ThreadTest { 10 public static void main(String[] args) { 11 //1. 利用陣列模擬兩個病人 12 SickPerson[] arr = new SickPerson[]{new SickPerson("yase"), new SickPerson("daji")}; 13 14 Thread bianque = new Thread(new Runnable() { 15 @Override 16 public void run() { 17 System.out.println("扁鵲開始看病,,,,"); 18 //2. 使用 for 回圈,一個個的處理病人 19 for(int i = 0;i<arr.length;i++){ 20 System.out.println("處理" +arr[i].name+ ",需要5秒鐘,,,,,,"); 21 try { 22 Thread.sleep(5000);//讓當前執行緒睡一會兒,然后接著執行 23 } catch (InterruptedException e) { 24 } 25 } 26 System.out.println("結束,,,,,,"); 27 } 28 },"bianque"); 29 bianque.start(); 30 } 31 }
結果:
開啟兩個執行緒
1 //用陣列模擬兩個病人 2 SickPerson yase = new SickPerson("yase"); 3 SickPerson daji = new SickPerson("daji"); 4 5 // SickPerson[] arr = new SickPerson[]{new SickPerson("yase"),new SickPerson("daji")}; 6 //創建內部類Runnable重寫run方法,使用for回圈,處理病人, 執行緒睡眠,接著執行 7 Thread bianque = new Thread(new Runnable() { 8 @Override 9 public void run() { 10 System.out.println("扁鵲開始看病"); 11 System.out.println("給"+yase.name+"看病需要5秒"); 12 try { 13 Thread.sleep(5000); 14 } catch (InterruptedException e) { 15 } 16 } 17 },"bianque"); 18 bianque.start(); 19 //開啟第二個執行緒 20 Thread huatuo = new Thread(new Runnable() { 21 @Override 22 public void run() { 23 System.out.println("華佗開始看病"); 24 System.out.println("給"+daji.name+"看病要5秒"); 25 try { 26 Thread.sleep(5000); 27 } catch (InterruptedException e) { 28 throw new RuntimeException(e); 29 } 30 } 31 },"huatuo"); 32 huatuo.start();
結果:
5.資料安全問題
1.前提
之前我們了解到,CPU是不斷的切換執行緒執行的,當CPU從A執行緒切換到B執行緒時,A執行緒就會暫停執行,比如:
1 public void run() { 2 //當執行緒執行到第一句代碼時,CPU很可能就切換其他執行緒執行,那么當前執行緒就會暫停 3 System.out.println(name + ",,,,,"); 4 System.out.println(name + ",,,,,"); 5 System.out.println(name + ",,,,,"); 6 }
2.多執行緒操作資料
當多個執行緒同時操作同一個資料時候,可能產生資料安全問題
示例:
1 class Hero implements Runnable{ 2 //1. 定義一個靜態變數,多個執行緒同時操作它 3 public static int num = 10; 4 @Override 5 public void run() { 6 while (true){// while中用true,這是死回圈,謹慎使用,這里是為了演示效果 7 //2. run方法中,對num--,當num<=0時,跳出回圈 8 if(num > 0){ 9 //sleep(5),讓當前執行緒休眠5毫秒,此時CPU會執行其他執行緒 10 try { Thread.sleep(5); } catch (InterruptedException e) {} 11 num--; 12 System.out.println(Thread.currentThread().getName() + "***********" + num); 13 }else{ 14 break; 15 } 16 } 17 } 18 } 19 public class ThreadDemo { 20 public static void main(String[] args) { 21 Hero hero = new Hero(); 22 Thread yase = new Thread(hero, "yase"); 23 Thread daji = new Thread(hero, "daji"); 24 //3. 開啟兩個執行緒操作num 25 yase.start(); 26 daji.start(); 27 } 28 }
結果:
代碼中,當 num>0 時,才會執行輸出陳述句,但是卻輸出了負數
分析一下執行程序:
- 2個執行緒剛開始正常執行,都會執行num--,,,,,
- 當num=1時,假如 'yase' 先進入 if ,休眠5毫秒,這時 CPU 切換執行緒‘daji’進入 if
- ‘yase’休眠結束,執行num--,接著‘daji’睡醒后也執行num--,最終num=-1
- 注意:即使沒有 sleep 陳述句,也可能輸出負數,只不過概率太低
總結:一個執行緒在操作資料時,其他執行緒也參與運算,最終可能造成資料錯誤
解決:保證操作資料的代碼在某一時間段內,只被一個執行緒執行,執行期間,其他執行緒不能參與,即使用synchronized關鍵字
6.執行緒同步
執行緒同步:就是讓執行緒一個接一個的排隊執行
- 同步:即一步一步,也是一個接一個的意思
java中提供 synchronized 關鍵字用來實作同步,可以解決多執行緒時的資料安全問題
不過,使用 synchronized 的方式有很多種,我們一個一個解釋
1.synchronized代碼塊
格式:synchronized(鎖物件){
同步代碼塊
}
鎖物件:可以理解為鑰匙、通行證,只有執行緒拿到通行證后,才能執行 { } 中的代碼
-
演示
使用 synchronized 修改 run 方法:
1 public static Object obj = new Object(); 2 @Override 3 public void run() { 4 while (true){ 5 //使用同步代碼塊,obj被稱為鎖物件 6 synchronized (obj){ 7 if(num > 0){ 8 //sleep(5),讓當前執行緒休眠5毫秒,此時CPU會執行其他執行緒 9 try { Thread.sleep(5); } catch (InterruptedException e) {} 10 num--; 11 System.out.println(Thread.currentThread().getName() + "***********" + num); 12 }else{ 13 break; 14 } 15 } 16 } 17 }
結果:
原因:
- 當執行緒執行到 synchronized (obj) 時,會獲取嘗試 obj 物件
- synchronized 保證多執行緒下,只有一個執行緒能獲取到obj物件,其他執行緒就會阻塞(暫停)
- 多個執行緒同時執行到 synchronized (obj) 這句代碼時,只有一個執行緒能夠拿到obj,其他執行緒暫停
- 當持有 obj 的執行緒從 synchronized 退出后,會釋放 obj 物件,然后其他執行緒再次爭奪 obj
這樣就保證了 synchronized 中的代碼在某一時刻只能被一個執行緒執行
- 好處和弊端
好處:解決多執行緒資料安全問題
弊端:同步代碼塊同時只能被一個執行緒執行,降低效率
synchronized注意事項
- 必須是多個執行緒
- 執行緒數>1,就是多執行緒
- 多執行緒爭搶的鎖物件只能有一個,下圖中的做法要堅決抵制(會被開哦)

2.同步方法
把 synchronized 放到方法上,那么這個方法就是同步方法
- 演示
1 class Hero implements Runnable{ 2 public static int num = 10; 3 public static Object obj = new Object(); 4 @Override 5 public void run() { 6 //run方法中呼叫同步方法 7 this.show(); 8 } 9 //同步方法 10 public synchronized void show(){ 11 while (true){ 12 if(num > 0){ 13 try { Thread.sleep(5); } catch (InterruptedException e) {} 14 num--; 15 System.out.println(Thread.currentThread().getName() + "***********" + num); 16 }else{ 17 break; 18 } 19 } 20 } 21 22 } 23 public class ThreadDemo { 24 public static void main(String[] args) { 25 Hero hero = new Hero(); 26 Thread yase = new Thread(hero, "yase"); 27 Thread daji = new Thread(hero, "daji"); 28 yase.start(); 29 daji.start(); 30 } 31 }
結果:
2.同步方法的鎖物件
使用同步代碼塊時,需要一個鎖物件,但是同步方法并不需要,因為:同步方法的鎖是this(當前物件)
證明:
1 class Hero implements Runnable{ 2 public static Object obj = new Object(); 3 @Override 4 public void run(){ 5 //1. 使用 this 作為鎖物件 6 synchronized (this){ 7 try { Thread.sleep(2000); } catch (InterruptedException e) {} 8 System.out.println(Thread.currentThread().getName() + ",,,,,,,"+this); 9 } 10 } 11 //2. 同步方法的鎖是 this 12 public synchronized void show(){ 13 System.out.println(Thread.currentThread().getName() + ",,,,,,,"+this); 14 } 15 16 } 17 public class ThreadDemo { 18 public static void main(String[] args) throws Exception { 19 Hero hero = new Hero(); 20 //3. 啟動 ‘亞瑟’ 執行緒,自動執行run方法 21 new Thread(hero,"亞瑟").start(); 22 23 //4. 主執行緒休眠100毫秒后,執行show這個同步方法 24 Thread.sleep(100); 25 hero.show(); 26 } 27 }
上面,run方法中的 this 就是 hero物件,結果:

原因:
- 亞瑟執行緒先執行,走到 Thread.sleep(2000) 時,休眠2秒,但這時它已經持有 this (也就是hero物件)
- main執行緒休眠100毫秒后,執行show方法嘗試獲取this,無法獲取到,于是等待1.9秒
- 所以最終結果,,,,,
3.靜態同步方法
synchronized 放到靜態方法上,就是靜態同步方法,鎖物件是:Hero.class
- Hero.class 也是物件,稱為Class物件或位元組碼物件,是jvm把Hero.class加載到記憶體后創建一個物件

修改代碼:
1 class Hero implements Runnable{ 2 public static Object obj = new Object(); 3 @Override 4 public void run(){ 5 //1. 鎖物件換成了Hero.class,也就是Hero位元組碼物件 6 synchronized (Hero.class){ 7 try { Thread.sleep(2000); } catch (InterruptedException e) {} 8 System.out.println(Thread.currentThread().getName() + ",,,,,,,"+Hero.class); 9 } 10 } 11 //2. show方法改成static 12 public static synchronized void show(){ 13 System.out.println(Thread.currentThread().getName() + ",,,,,,,"+Hero.class); 14 } 15 16 } 17 public class ThreadDemo { 18 public static void main(String[] args) throws Exception { 19 Hero hero = new Hero(); 20 new Thread(hero,"亞瑟").start(); 21 22 Thread.sleep(100); 23 Hero.show(); 24 } 25 }
結果:
4.死鎖
A 執行緒等待 B 執行緒釋放鎖,同時 B 執行緒也在等到 A 執行緒釋放鎖,比如:

通常發生在同步嵌套
- 同步嵌套:synchronized 中 還有 synchronized
示例:
1 class Hero implements Runnable{ 2 //1. 搞兩個鎖物件 A、B 3 public static Object A = new Object(); 4 public static Object B = new Object(); 5 6 public boolean bool; 7 Hero(boolean bool){ 8 this.bool = bool; 9 } 10 @Override 11 public void run(){ 12 //2. 當 bool 是 true 13 if(bool){ 14 synchronized (A){//先獲取 A 鎖,然后睡一會兒,再獲取 B 鎖 15 System.out.println(Thread.currentThread().getName() + "------拿到A鎖,準備獲取B鎖,,,,,,,"); 16 try { Thread.sleep(200); } catch (InterruptedException e) {} 17 synchronized (B){ 18 System.out.println(Thread.currentThread().getName() + ",,,,,,,"); 19 } 20 } 21 }else{//3. 當 bool 是 false 22 synchronized (B){ //先獲取B鎖,睡一會兒,再獲取A鎖 23 System.out.println(Thread.currentThread().getName() + "------拿到B鎖,準備獲取A鎖,,,,,,,"); 24 try { Thread.sleep(200); } catch (InterruptedException e) {} 25 synchronized (A){ 26 System.out.println(Thread.currentThread().getName() + ",,,,,,,"); 27 } 28 } 29 } 30 } 31 } 32 public class ThreadDemo { 33 public static void main(String[] args) throws Exception { 34 new Thread(new Hero(true), "yase").start(); 35 new Thread(new Hero(false), "daji").start(); 36 } 37 }
結果:

上圖中,死鎖導致程式無法繼續運行,但同時也一直不結束
執行程序分析:
- 假設 yase 執行緒先執行,在獲取的A鎖后,睡覺
- daji執行緒接著執行,在獲取B鎖后,睡覺
- yase執行緒睡醒后,嘗試獲取B鎖,但是已經被daji執行緒拿到了,于是阻塞
- daji執行緒睡醒后,嘗試獲取A鎖,但是已經被yase執行緒拿到了,也阻塞
- yase執行緒需要獲取B鎖后,執行完代碼,再能釋放A鎖
- daji執行緒需要獲取A鎖后,執行完代碼,再能釋放B鎖
- 最終兩個執行緒都阻塞,程式卡死
總結:死鎖是開發中的禁忌,絕對禁止出現,所以開發中盡量避免同步代碼塊嵌套使用
7.等待喚醒機制
1.體驗
Object 類中有 wait()、notify() 這兩個方法
- wait():讓當前執行緒進入等待狀態
- 使用方式:鎖物件.wait(),必須放到 同步代碼塊 或 同步方法 中
- notify():喚醒對應的正在wait()的執行緒
- 使用方式:鎖物件.notify(),必須放到 同步代碼塊 或 同步方法 中
- 呼叫 wait() 和 notify() 的鎖物件必須是同一個
作用:使用這兩個方法可以讓多個執行緒間產生通信
示例:
1 class Hero implements Runnable{ 2 //1. 搞一個鎖物件 3 public static Object lock = new Object(); 4 @Override 5 public void run(){ 6 synchronized (lock){ 7 System.out.println(Thread.currentThread().getName() + ",,,,,,,獲取鎖,然后進入等待狀態"); 8 try { 9 //2. 當前執行緒等待,使用方式:鎖物件.wait(),而且必須放到同步代碼塊或者同步方法中 10 lock.wait(); 11 } catch (InterruptedException e) { } 12 System.out.println(Thread.currentThread().getName() + ",,,,,,,結束"); 13 } 14 } 15 } 16 public class ThreadDemo { 17 public static void main(String[] args) throws Exception { 18 //3. 創建一個 yase 執行緒 19 Thread yase = new Thread(new Hero(),"yase"); 20 yase.start(); 21 22 //4. 主執行緒休眠2秒 23 Thread.sleep(2000); 24 25 //5. 使用notify喚醒yase執行緒,讓他繼續執行 26 synchronized (Hero.lock){ 27 System.out.println("主執行緒喚醒yase,,,,,,,"); 28 //使用方式:鎖物件.notify(),喚醒對應的正在等待狀態的執行緒,必須放到同步代碼塊或者同步方法中 29 Hero.lock.notify(); 30 } 31 } 32 }
結果:
2.注意:
1.wait、notify必須放到同步代碼塊或同步方法中
2.必須是:鎖物件.wait(),如果鎖物件是this,那么:this.wait()
3.必須是:鎖物件.notify()
4.呼叫 wait() 和 notify() 的鎖物件必須是同一個
如果鎖物件不一樣,程式則不會結束 原因:a. Hero.class.notify();只能喚醒鎖物件是Hero.class 且執行了Hero.class.wait()的執行緒
b. 而yase執行緒的鎖物件是lock,執行了lock.wait(),一直在等待被人喚醒,所以程式一直不結束 5.執行 notify 后,正在 wait 的執行緒并不是立即運行,需要等待執行 notify 的執行緒釋放鎖 6.wait()等待時候,會釋放鎖物件
上圖可以看出,yase先執行,如果wait()不釋放鎖,那么就無法執行Hero.lock.notify()這句代碼
7.如果有多個執行緒都在wait(),notify只會隨機的喚醒一個
1 public static void main(String[] args) throws Exception { 2 //創建 2 個執行緒 3 Thread yase = new Thread(new Hero(),"yase"); 4 yase.start(); 5 Thread laoyase = new Thread(new Hero(),"laoyase"); 6 laoyase.start(); 7 Thread.sleep(2000); 8 9 //使用方式:鎖物件.notify(),喚醒對應的正在等待狀態的執行緒,必須放到同步代碼塊或者同步方法中 10 synchronized (Hero.lock){ 11 System.out.println("主執行緒喚醒,,,,,,,"); 12 Hero.lock.notify();//有多個執行緒都在wait(),notify只會隨機的喚醒一個 13 } 14 }
結果:
上圖,yase執行緒結束了,但是laoyase這個執行緒還在阻塞
7.notifyAll()
如果想全部喚醒,可以使用notifyAll(),或者執行多次notify(),比如:

結果:
執行多次notify()方法也可以,全部喚醒
8.特殊情況下的notify會喚醒所有
1 class Hero extends Thread{ 2 public void run(){ 3 //1. 注意這里的鎖物件是this 4 synchronized (this){ 5 //2. 執行 notify 喚醒所有 6 this.notify(); 7 System.out.println("lock執行緒運行----"+this.getName()); 8 } 9 } 10 } 11 public class ThreadDemo { 12 public static void main(String[] args) throws Exception { 13 //3. 創建一個Thread物件,作為鎖物件 14 Hero yase = new Hero(); 15 yase.setName("yase"); 16 17 new Thread(new Runnable() { 18 @Override 19 public void run() { 20 System.out.println("執行緒daji開始運行,,,,,,"); 21 //4. 注意,這里的鎖物件是一個執行緒物件 22 synchronized (yase){ 23 try { 24 yase.wait(); 25 System.out.println("執行緒daji結束----"+yase.getName());//輸出lock執行緒的名稱 26 } catch (InterruptedException e) { 27 } 28 } 29 } 30 }, "daji").start(); 31 32 new Thread(new Runnable() { 33 @Override 34 public void run() { 35 System.out.println("執行緒lvbu開始運行,,,,,,"); 36 //4. 注意,這里的鎖物件是一個執行緒物件 37 synchronized (yase){ 38 try { 39 yase.wait(); 40 System.out.println("執行緒vbul結束----"+yase.getName()); 41 } catch (InterruptedException e) { 42 } 43 } 44 } 45 }, "lvbu").start(); 46 47 Thread.sleep(2000); 48 //5. 啟動 yase 執行緒 49 yase.start(); 50 } 51 }
結果:
代碼分析:
- 首先創建一個yase執行緒物件
- 然后創建了 daji、lvbu 兩個執行緒,這兩個執行緒都把 yase 物件作為鎖物件
- 這兩個執行緒分別啟動后都執行 wait 方法,進入等待模式
- 最后yase執行緒啟動,執行run方法時,呼叫 yase.notify();
- 結果:daji、lvbu 都被喚醒
3.生產消費模式

wait()、notify() 經常用于生產消費模式,這是作業中最常見的設計方案:生產者生產資料,消費者消費資料
需求:生產者生產商品,如果貨架上已經有商品就不再生產,消費者消費商品,如果貨架上沒有就通知生產者
代碼:
1 class Goods{ 2 String name = "哈根達斯"; 3 4 //0:表示貨架上沒有商品,需要生產 5 //1:表示貨架上已有商品,需要消費 6 int count = 0; 7 } 8 //消費者 9 class Consumer implements Runnable{ 10 private Goods goods; 11 Consumer(Goods goods){ 12 this.goods = goods; 13 } 14 @Override 15 public void run(){ 16 while (true){ 17 synchronized (goods){ 18 if(goods.count == 1){ 19 System.out.println(Thread.currentThread().getName()+",,,,,,消費商品---"+goods.count); 20 goods.count = 0;//消費商品 21 goods.notify();//喚醒消費者 22 //讓自己自己等待 23 try {goods.wait();} catch (InterruptedException e) {} 24 } 25 } 26 } 27 } 28 } 29 //生產者 30 class Producer implements Runnable{ 31 32 private Goods goods; 33 Producer(Goods goods){ 34 this.goods = goods; 35 } 36 @Override 37 public void run(){ 38 while (true){ 39 synchronized (goods){ 40 if(goods.count == 0){ 41 System.out.println(Thread.currentThread().getName()+",,,,,,生產商品---------"+goods.count); 42 goods.count = 1;//生產商品,設定count=1 43 goods.notify();//喚醒消費者 44 //自己等待 45 try {goods.wait();} catch (InterruptedException e) {} 46 } 47 } 48 } 49 } 50 } 51 public class ThreadDemo { 52 public static void main(String[] args) throws Exception { 53 Goods goods = new Goods(); 54 new Thread(new Producer(goods), "伊利").start(); 55 new Thread(new Consumer(goods), "亞瑟").start(); 56 } 57 }
結果:
4.wait和sleep
相同點:
- 都可以暫時停止一個執行緒
不同點:
- sleep必須指定睡眠時間,wait可以指定也可以不指定
- sleep是 Thread 的靜態方法,wait是 Object 類的成員方法
- sleep 不釋放鎖,wait 釋放鎖
- sleep 等待一定時間后自定運行,wait需要 notify 或 notifyAll 喚醒
- wait 必須配合synchronized使用,sleep不必
- sleep 會讓執行緒進入 TIMED_WAITING 狀態,wait讓執行緒進入 WAITING 狀態
- 之后會詳細解釋執行緒狀態
8.停止執行緒
Thread類中有一個 stop 方法,可以停止執行緒,但是已經過時,不推薦使用
其實,停止執行緒的最好方式是讓執行緒正常結束
具體方式:
- 宣告一個變數,設定一個開關
1 class Hero extends Thread{ 2 //1. 定義一個boolean值,控制執行緒是否結束 3 boolean bool; 4 public void run(){ 5 for (int i = 0; i < 10; i++) { 6 if(bool){ 7 //2. 當bool=true時,跳出回圈,run方法結束,執行緒也就停止了 8 break; 9 } 10 System.out.println(Thread.currentThread().getName()+",,,,,,"+i); 11 try {Thread.sleep(500);} catch (InterruptedException e) {} 12 } 13 System.out.println(Thread.currentThread().getName()+"執行緒結束,,,,,,"); 14 } 15 } 16 public class ThreadDemo { 17 public static void main(String[] args) throws Exception { 18 Hero hero = new Hero(); 19 hero.start(); 20 21 Thread.sleep(3000); 22 System.out.println("main執行緒休息2秒后,設定bool=true,,,,,,"); 23 hero.bool = true; 24 } 25 }
結果:
- interrupt 方法
Thread 中有個 interrupt 方法,可以用來終止執行緒
- 注意:interrupt 并不會終止執行緒,它只是將執行緒的中斷標記設為true
- 使用 isInterrupted() 方法判斷執行緒的終端標記是否為 true
1 class Hero extends Thread{ 2 public void run(){ 3 for (int i = 0; i < 100000000; i++) { 4 //如果當前執行緒的中斷標記是true,跳出回圈,執行緒結束 5 if(Thread.currentThread().isInterrupted()){ 6 System.out.println(Thread.currentThread().getName()+"被打斷,跳出回圈,,,,,,"); 7 break; 8 } 9 System.out.println(Thread.currentThread().getName()+",,,,,,"+i); 10 } 11 System.out.println(Thread.currentThread().getName()+"執行緒結束,,,,,,"); 12 } 13 } 14 public class ThreadDemo { 15 public static void main(String[] args) throws Exception { 16 Hero hero = new Hero(); 17 hero.start(); 18 19 Thread.sleep(1000); 20 System.out.println("main執行緒休息1秒后,執行interrupt,,,,,,"); 21 hero.interrupt(); 22 } 23 }
結果:
- interrupt 和 sleep
上面代碼中我們把sleep陳述句給去掉了,這時因為,如果執行緒在 sleep、wait 時被 interrupt
會拋出 InterruptedException,比如:

結果:
9.守護執行緒
目前我們創建的執行緒是前臺執行緒,也叫一般執行緒,執行緒中還有一種比較特殊的:守護執行緒
- 通過 setDaemon 方法可以把一個執行緒設定為守護執行緒
守護執行緒跟一般執行緒差不多,只是結束的時有些區別
- 當前臺執行緒結束,守護執行緒會自動結束
示例:
1 public static void main(String[] args) throws Exception { 2 //1. 創建一個執行緒 3 Thread yase = new Thread(new Runnable() { 4 @Override 5 public void run() { 6 for (int i = 0; i < 100; i++) { 7 System.out.println(Thread.currentThread().getName()+",,,,,,"+i); 8 try { 9 Thread.sleep(500); 10 } catch (InterruptedException e) { 11 } 12 } 13 System.out.println("yase執行緒結束,,,,,,,"); 14 } 15 },"yase"); 16 //2. 設定yase是守護執行緒 17 yase.setDaemon(true); 18 yase.start(); 19 20 //3. main是一般執行緒,休眠3秒后結束 21 Thread.sleep(3000); 22 System.out.println("main執行緒休息3秒后,over,,,,,,"); 23 }
結果:
main執行緒結束后,守護執行緒自動結束
另外,當所有前臺執行緒結束后,守護執行緒才會結束
示例:
1 public static void main(String[] args) throws Exception { 2 //1. 創建一個執行緒 3 Thread yase = new Thread(new Runnable() { 4 @Override 5 public void run() { 6 for (int i = 0; i < 100; i++) { 7 System.out.println(Thread.currentThread().getName()+",,,,,,"+i); 8 try { 9 Thread.sleep(500); 10 } catch (InterruptedException e) { 11 } 12 } 13 System.out.println("yase執行緒結束,,,,,,,"); 14 } 15 },"yase"); 16 //2. 設定yase是守護執行緒 17 yase.setDaemon(true); 18 yase.start(); 19 20 //3. daji是一般執行緒 21 Thread daji = new Thread(new Runnable() { 22 @Override 23 public void run() { 24 for (int i = 0; i < 20; i++) { 25 System.out.println(Thread.currentThread().getName()+"-----------------------"+i); 26 try { 27 Thread.sleep(500); 28 } catch (InterruptedException e) { 29 } 30 } 31 System.out.println("daji執行緒結束,,,,,,,"); 32 } 33 },"daji"); 34 daji.start(); 35 36 37 //4. main是一般執行緒,休眠3秒后結束 38 Thread.sleep(3000); 39 System.out.println("main執行緒休息3秒后,over,,,,,,"); 40 }
結果:
上圖,main執行緒執行最后一句代碼后,yase 和 daji 執行緒依舊在運行

當 daji執行緒結束后,yase這個守護執行緒也隨之結束
10.執行緒狀態
Thread類中有 State 列舉類,羅列了執行緒狀態

1. 初始(NEW):新創建了一個執行緒物件,但還沒有呼叫start()方法
2. 運行(RUNNABLE):Java執行緒中將就緒(ready)和運行中(running)兩種狀態籠統的稱為“運行”
- 執行緒呼叫了start()方法后,這時執行緒位于可運行執行緒池中,等待獲取CPU的使用權,此時處于就緒狀態(ready)
- 就緒狀態的執行緒在獲得CPU時間片后變為運行中狀態(running)
3. 阻塞(BLOCKED):表示執行緒等待獲取鎖
- 遇到synchronized變為阻塞狀態
4. 等待(WAITING):進入該狀態的執行緒需要等待其他執行緒做出一些特定動作(喚醒或打斷)
- 遇到 wait、join 變為等待狀態
5. 超時等待(TIMED_WAITING):跟WAITING不同,可在指定時間后自己運行
- 遇到wait(毫秒數)變為超時等待
- 如果在等待時被喚醒,立即執行
- 等待超時后,先嘗試獲取鎖,不能獲取則阻塞,獲取到就運行
6. 終止(TERMINATED):表示該執行緒已經執行完畢
狀態切換時常用方法
|
start() |
啟動一個執行緒 |
|
run() |
執行緒需要執行的代碼,run方法結束,該執行緒結束 |
|
sleep(long millis) |
執行緒休眠,但不釋放鎖 |
|
join() |
等待該執行緒結束,可以讓執行緒順序執行 |
|
wait()/notify()/notifyAll() |
wait()使當前執行緒等待,前提是 必須先獲得鎖,一般配合synchronized 關鍵字使用 |
4.設計模式
1.概述
設計模式:就是解決問題的方案,是前輩們對解決某些問題的一些經驗總結
好處:提高代碼重用性、健壯性,讓代碼更容易被他人理解
目前 java 中比較流行的有23種設計模式
2.單例模式
場景:多個程式需要操作同一個物件,程式A 修改后,程式B 需要拿到物件中的最新資料繼續操作
如何實作?
- 類的外部不能通過 new 關鍵字創建物件,所以構造方法應該是 private
- 在這個類中自己 new 一個物件,并且提供 get 方法,讓外部可以獲取
示例:餓漢模式
1 class Single{ 2 3 //1. 私有的構造方法,外部不能使用,但是本類中可以使用 4 private Single(){ 5 } 6 //2. 搞一個靜態變數,這樣類初始化時候,instance就已經有值了 7 //設定為private,是避免外部修改這個變數,當然也可以使用final 8 private static Single instance = new Single(); 9 10 //3. 對外提供獲取物件的方式,這樣每次呼叫這個方法拿到的都是同一個物件 11 public static Single getInstance(){ 12 return instance; 13 } 14 }
懶漢模式
1 class Single{ 2 3 //1. 私有的構造方法,外部不能使用,但是本類中可以使用 4 private Single(){ 5 } 6 //2. 搞一個靜態變數 7 private static Single instance = null; 8 9 //3. 對外提供獲取物件的方式 10 public static Single getInstance(){ 11 //如果 instance 是空的時候,在創建物件,這就是懶漢式 12 if(instance == null){ 13 synchronized (Single.class){ 14 if(instance == null){ 15 instance = new Single(); 16 } 17 } 18 } 19 return instance; 20 } 21 //方法中用了兩次if判斷,這就是經典的雙檢鎖 22 }
11.多個執行緒順序執行(了解)
某些情況下雖然開啟了多個執行緒,但是還是希望他們能個一個接一個的執行,這就是順序執行
Thread中有一個 join 方法,使用它可以讓執行緒順序執行
1 public static void main(String[] args) throws Exception { 2 //1. 多個執行緒之間順序執行 3 Thread t1 = new Thread(new Runnable() { 4 @Override 5 public void run() { 6 System.out.println("執行緒1執行,,,,,,"); 7 } 8 }); 9 Thread t2 = new Thread(new Runnable() { 10 @Override 11 public void run() { 12 System.out.println("執行緒2執行,,,,,,"); 13 } 14 }); 15 Thread t3 = new Thread(new Runnable() { 16 @Override 17 public void run() { 18 System.out.println("執行緒3執行,,,,,,"); 19 } 20 }); 21 22 t1.start(); 23 t1.join();//執行join方法后,main執行緒等待t1結束后,才繼續往下執行 24 t2.start(); 25 t2.join(); 26 t3.start(); 27 t3.join(); 28 }
結果:
上面的代碼一定要注意:t1.join()這句代碼是由main執行緒執行的,所以main執行緒會等待
12.執行緒優先級(了解)
執行緒有優先級,優先級高的獲取CPU執行權的概率就大
- 通過getPriority、setPriority 獲取和設定執行緒的優先級,最大是10,最小是1
- 如果不設定,默認的優先級是5
-
1 class Hero extends Thread{ 2 @Override 3 public void run() { 4 for (int i = 0; i < 1000000; i++) { 5 System.out.println(Thread.currentThread().getName()+",,,,,,"+i); 6 } 7 } 8 } 9 public class ThreadDemo { 10 public static void main(String[] args) throws Exception { 11 Thread yase = new Thread(new Hero(),"yase"); 12 yase.setPriority(10);//設定優先級,10最大,1最小 13 yase.start(); 14 15 Thread daji = new Thread(new Hero(), "daji"); 16 daji.setPriority(1);//設定優先級,10最大,1最小 17 daji.start(); 18 19 //主執行緒多睡一會兒 20 Thread.sleep(60000); 21 } 22 }
結果:
yase執行緒執行到了54萬,daji執行緒才執行到39萬,明顯yase執行緒有更高的CPU執行權
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/545688.html
標籤:Java
上一篇:Redis分布式鎖常見坑點分析
