主頁 > 後端開發 > 學習多執行緒的心得

學習多執行緒的心得

2023-03-04 07:03:37 後端開發

 

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 點:

  1. main 方法中的代碼都是有 main 執行緒執行的
  2. 在 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.設定執行緒名

如果對默認的名稱不滿意,也可以在創建物件的時候自定義執行緒名稱

  1. 繼承 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. 除錯方便:當程式運行出現問題時,如果每個執行緒都有自己的名稱,可以快速定位問題
  2. 可讀性提高:如果代碼中存在多個執行緒,如果有名稱可以使得代碼更易于理解和維護,
  3. 日志記錄方便:當出現問題時,如果執行緒有名稱,根據日志可以更方便地識別每個執行緒的相關資訊

例:

 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. 宣告一個變數,設定一個開關
 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 }

結果:

  1. 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 }

結果:

  1. 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 關鍵字使用
只有當 notify/notifyAll() 被執行時候,才會喚醒該執行緒繼續執行,直到執行完synchronized 代碼塊或是再次遇到wait()
notify/notifyAll() 的執行只是喚醒等待的執行緒,而不會立即釋放鎖,鎖的釋放要看代碼塊的具體執行情況,所以盡量在使用了notify/notifyAll() 后立即退出臨界區,以喚醒其他執行緒讓其獲得鎖

 4.設計模式

1.概述

設計模式:就是解決問題的方案,是前輩們對解決某些問題的一些經驗總結

好處:提高代碼重用性、健壯性,讓代碼更容易被他人理解

目前 java 中比較流行的有23種設計模式

2.單例模式

場景:多個程式需要操作同一個物件,程式A 修改后,程式B 需要拿到物件中的最新資料繼續操作

如何實作?

  1. 類的外部不能通過 new 關鍵字創建物件,所以構造方法應該是 private
  2. 在這個類中自己 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分布式鎖常見坑點分析

下一篇:網路通信——TCP “三次握手“、“四次揮手“ 詳解

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more