大綱 學前必讀 學習不能快速成功,但一定可以快速入門整體課程思路:1.實踐為主,理論化偏少2.課程筆記有完整的案例和代碼,(為了學習效率)再開始之前我會簡單粗暴的介紹知識點案例思路, 有基礎的同學聽了之後可以直接結合筆記寫代碼, 如果沒聽懂再向下看視頻,我會手把手編寫代碼和演示測試結果;3.重要提示 ...
大綱
-------------------------學前必讀----------------------------------
學習不能快速成功,但一定可以快速入門
整體課程思路:
1.實踐為主,理論化偏少
2.課程筆記有完整的案例和代碼,(為了學習效率)再開始之前我會簡單粗暴的介紹知識點案例思路,
有基礎的同學聽了之後可以直接結合筆記寫代碼,
如果沒聽懂再向下看視頻,我會手把手編寫代碼和演示測試結果;
3.重要提示,學編程和學游泳一樣,多實踐學習效率才高,理解才透徹;
4.編碼功底差的建議每個案例代碼寫三遍,至於為什麼...<<賣油翁>>...老祖宗的智慧
-------------------------------------------------------------------------
1.線程
1.1.什麼是線程
線程(英語:thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。在Unix System V及SunOS中也被稱為輕量進程(lightweight processes),但輕量進程更多指內核線程(kernel thread),而把用戶線程(user thread)稱為線程。(來自百度百科)
一個進程可以有很多線程,每條線程並行執行不同的任務。
1.2.多線程hello word
需求:模擬在計算上一邊聽歌一邊打游戲
三種實現方案如下:
TestDemo1 package com.wfd360.thread; 2 3 import com.wfd360.thread.demo01.GameRunnable; 4 import com.wfd360.thread.demo01.MusicRunnable; 5 import com.wfd360.thread.demo02.GameThread; 6 import com.wfd360.thread.demo02.MusicThread; 7 import org.junit.Test; 8 9 /** 10 * @author 姿勢帝-博客園 11 * @address https://www.cnblogs.com/newAndHui/ 12 * @WeChat 851298348 13 * @create 05/03 5:27 14 * @description 需求分析: 15 * 1.模擬一邊打游戲一邊聽音樂,在控制台列印輸出模擬 16 * 2.把兩個業務封裝成獨立的線程,實現介面Runnable或繼承Thread,通過看源碼你會發現Thread類實現了介面Runnable,使用本質上這兩種方法時一樣的。 17 * 3.Thread類提供兩個方法,線程主題方法run,啟動線程方法start 18 */ 19 public class TestDemo { 20 /** 21 * 方式1:實現介面Runnable 22 */ 23 @Test 24 public void testRunnable() throws InterruptedException { 25 System.out.println("-------test start-------"); 26 // 實例對象 27 MusicRunnable music = new MusicRunnable(); 28 GameRunnable game = new GameRunnable(); 29 // 創建線程 30 Thread musicThread = new Thread(music); 31 Thread gameThread = new Thread(game); 32 // 啟動線程 33 musicThread.start(); 34 gameThread.start(); 35 System.out.println("--------等待其他線程執行--------------"); 36 Thread.sleep(5 * 1000); 37 System.out.println("-------test end-------"); 38 } 39 40 /** 41 * 方式2:繼承Thread 42 */ 43 @Test 44 public void testThread() throws InterruptedException { 45 System.out.println("-------test start-------"); 46 // 創建線程 47 MusicThread musicThread = new MusicThread(); 48 GameThread gameThread = new GameThread(); 49 // 啟動線程 50 musicThread.start(); 51 gameThread.start(); 52 System.out.println("--------等待其他線程執行--------------"); 53 Thread.sleep(5 * 1000); 54 System.out.println("-------test end-------"); 55 } 56 57 /** 58 * 方式3:簡寫,這種寫法一般我們在做模擬測試的使用,在正式代碼中建議不使用,可讀性較差 59 */ 60 @Test 61 public void testThreadSimple() throws InterruptedException { 62 System.out.println("-------test start-------"); 63 // 創建線程 64 Thread musicThread = new Thread(() -> { 65 for (int i = 0; i < 100; i++) { 66 System.out.println("=======聽音樂中============" + i); 67 } 68 }); 69 Thread gameThread = new Thread(() -> { 70 for (int i = 0; i < 100; i++) { 71 System.out.println("=======打游戲中============" + i); 72 } 73 }); 74 // 啟動線程 75 musicThread.start(); 76 gameThread.start(); 77 System.out.println("--------等待其他線程執行--------------"); 78 Thread.sleep(5 * 1000); 79 System.out.println("-------test end-------"); 80 } 81 }
實現介面Runnable
1 package com.wfd360.thread.demo01; 2 3 /** 4 * @author 姿勢帝-博客園 5 * @address https://www.cnblogs.com/newAndHui/ 6 * @WeChat 851298348 7 * @create 05/03 5:31 8 * @description 9 */ 10 public class GameRunnable implements Runnable { 11 @Override 12 public void run() { 13 for (int i = 0; i < 100; i++) { 14 System.out.println("=======打游戲中============" + i); 15 } 16 } 17 }GameRunnable
1 package com.wfd360.thread.demo01; 2 3 /** 4 * @author 姿勢帝-博客園 5 * @address https://www.cnblogs.com/newAndHui/ 6 * @WeChat 851298348 7 * @create 05/03 5:29 8 * @description 9 */ 10 public class MusicRunnable implements Runnable { 11 @Override 12 public void run() { 13 for (int i = 0; i < 100; i++) { 14 System.out.println("=======聽音樂中============"+i); 15 } 16 } 17 }MusicRunnable
繼承Thread
1 package com.wfd360.thread.demo02; 2 3 /** 4 * @author 姿勢帝-博客園 5 * @address https://www.cnblogs.com/newAndHui/ 6 * @WeChat 851298348 7 * @create 05/03 6:00 8 * @description 9 */ 10 public class GameThread extends Thread { 11 @Override 12 public void run() { 13 for (int i = 0; i < 100; i++) { 14 System.out.println("-------游戲中----------"+i); 15 } 16 } 17 }GameThread GameThread
1 package com.wfd360.thread.demo02; 2 3 /** 4 * @author 姿勢帝-博客園 5 * @address https://www.cnblogs.com/newAndHui/ 6 * @WeChat 851298348 7 * @create 05/03 6:00 8 * @description 9 */ 10 public class MusicThread extends Thread { 11 @Override 12 public void run() { 13 for (int i = 0; i < 100; i++) { 14 System.out.println("-------音樂中----------" + i); 15 } 16 } 17 }
總結
啟動線程兩種方式:
1.通過繼承Thread類
2.實現Runnable介面
使用哪種方式更好?
區別:
一個類如果繼承了其他類,就無法在繼承Thread類,在Java中,一個類只能繼承一個類,而一個類如果實現了一個介面,還可以實現其他介面,介面是可以多實現的,所以說
Runable的擴展性更強,但是繼承的方式更簡單,個人建議一般情況下使用Thread;
實現介面Runnable或繼承Thread,通過看源碼你會發現Thread類實現了介面Runnable,使用本質上這兩種方法是一樣的
啟動線程流程:
創建啟動線程的方式一:繼承Thread類
1.將業務方法封裝成線程對象,自定義類t extends Thread類;
2.覆寫run方法: 覆寫第一步中的run方法;
3.創建自定義對象t
4.啟動線程 t.start();
創建啟動線程方式二:實現Runnable介面
1.將業務方法封裝成線程對象,自定義類t implements Runnable介面;
2.實現第一步中的run方法
3.創建自定義對象t
4.啟動線程 new Thread(t).start();
1.3.對主線程與創建線程執行順序的理解
問題:
直接寫一個簡單的HelloWorld 程式,有沒有線程?
==>有一個主線程,在垃圾回收的時候,有gc 線程。
1 package com.wfd360.thread; 2 3 import org.junit.Test; 4 5 /** 6 * @author 姿勢帝-博客園 7 * @address https://www.cnblogs.com/newAndHui/ 8 * @WeChat 851298348 9 * @create 05/04 11:09 10 * @description <p> 11 * 問題: 12 * 直接寫一個簡單的HelloWorld 程式,有沒有線程? 13 * ==>有一個主線程,在垃圾回收的時候,有gc 線程。 14 * 結論:一旦線程啟動起來之後就是獨立的,和創建環境沒有關係; 15 * 啟動線程不能直接調用run方法,必須調用start方法; 16 * </p> 17 */ 18 public class TestDemo02 { 19 /** 20 * 如果把創建線程放在迴圈語句的 下 面,會交替出現嗎 21 * ==>否,因為主線程執行完成後才會啟動hello線程 22 * 23 * @throws Exception 24 */ 25 @Test 26 public void test1() throws Exception { 27 System.out.println("---test start-------"); 28 // 執行主線程 29 for (int i = 0; i < 100; i++) { 30 System.out.println("-----test1--------" + i); 31 } 32 // 啟動hello線程 33 new HelloThread().start(); 34 System.out.println("=======等待執行完成==========="); 35 Thread.sleep(5 * 1000); 36 System.out.println("---test end-------"); 37 } 38 39 /** 40 * 如果把創建線程放在迴圈語句的 上 面,會交替出現嗎 41 * ==>可能會,可能不會,可能出現for迴圈完之後,線程還沒有啟動完; 42 * 43 * @throws Exception 44 */ 45 @Test 46 public void test2() throws Exception { 47 System.out.println("---test start-------"); 48 // 啟動hello線程 49 new HelloThread().start(); 50 // 執行主線程 51 for (int i = 0; i < 100; i++) { 52 System.out.println("-----test1--------" + i); 53 } 54 System.out.println("=======等待執行完成==========="); 55 Thread.sleep(5 * 1000); 56 System.out.println("---test end-------"); 57 } 58 59 /** 60 * 採用內部類的方式定義一個hello線程對象 61 */ 62 class HelloThread extends Thread { 63 @Override 64 public void run() { 65 for (int i = 0; i < 100; i++) { 66 System.out.println("-----HelloThread--------" + i); 67 } 68 } 69 } 70 }TestDemo02
結論:一旦線程啟動起來之後就是獨立的,和創建環境沒有關係;
啟動線程不能直接調用run方法,必須調用start方法;
1.4.對sleep方法的理解
package com.wfd360.thread; /** * @author 姿勢帝-博客園 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 05/04 11:34 * @description <p> * Thread類的方法: * static void sleep(long millis) 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行); * </p> */ public class TestSleep { /** * 做一個簡易倒計時,10秒鐘,控制台每一秒輸出一個數字,如10,9,8,7.....0 */ public static void main(String[] args) throws Exception { System.out.println("---test start-------"); for (int i = 10; i >= 0; i--) { Thread.sleep(1 * 1000); System.out.println(i); } System.out.println("---test end-------"); } }
1.5.線程名稱的設置與獲取
繼承方式
簡單需求:使用多線程模擬多視窗售票
1 package com.wfd360.thread.demo03Ticket; 2 3 /** 4 * @author 姿勢帝-博客園 5 * @address https://www.cnblogs.com/newAndHui/ 6 * @WeChat 851298348 7 * @create 05/04 11:55 8 * @description <p> 9 * 模擬多線程售票 10 * </p> 11 */ 12 public class TicketThread extends Thread { 13 // 假定票總是100張 14 private static Integer num = 100; 15 16 @Override 17 public void run() { 18 // 只要有票就一直售票 19 while (num > 0) { 20 System.out.println("正在出售第" + num + "張票"); 21 --num; 22 } 23 System.out.println("===售票結束==="); 24 } 25 }TicketThread
test
/** * 測試模擬三個視窗售票 * @throws InterruptedException */ @Test public void testTicketThread() throws InterruptedException { System.out.println("---test start-------"); // 模擬多3個視窗售票 TicketThread ticketThread1 = new TicketThread(); TicketThread ticketThread2 = new TicketThread(); TicketThread ticketThread3 = new TicketThread(); // 啟動線程售票 ticketThread1.start(); ticketThread2.start(); ticketThread3.start(); System.out.println("======等待售票============"); Thread.sleep(5 * 1000); System.out.println("---test end-------"); }
結果:
1.在售票過程中不能區分售出的票是那個視窗售出的,解決通過線程名稱判斷
2.有重覆售出的票(後面的線程同步解決)
解決第一個問題,設置獲取線程名稱,通過Thread對象裡面自帶的getName,setName方法
具體代碼
設置線程名稱
獲取線程名稱
上面講了繼承的方式獲取線程名稱,那麼實現介面Runnable的方式怎麼獲取設置勒
繼承Thread的方式,可以通過getName的方式獲取當前線程的名稱?
那使用Runnable的方式,能通過getName獲取嘛?
getName方法是Thread類的,但是TicketThread現在並沒有繼承Thread類,而是實現了Runnable介面.
問題:如果實現Runnable介面,怎麼獲取線程名稱?
思考:TicketThread類裡面的代碼要執行,它肯定存在於某個線程中, 就比如寫個helloword列印語句,是不是也處於一個主線程中,那這裡怎麼獲取線程名稱?
通過動態獲取,當程式正在執行的時候,獲取當前正在執行的線程名稱。怎麼獲取?
在Thread類裡面有個靜態的方法currentThread() 方法,返回當前正在執行的線程引用;
Thread.currentThread().getName
那怎麼設置線程名稱?
Thread類裡面有個name欄位,相當於Thread類把它包裝了一下:
通過源碼可以發現,構造方法裡面還有可以傳一個名字:
具體實現代碼如下
總結:
繼承方式設置\獲取線程名稱通過 Thread對象裡面的 setName,getName方法;
實現介面方式設置名稱通過 new Thread('線程實例對象', "線程名稱"),獲取線程名稱通過:Thread.currentThread().getName
1.6.Thread的join方法
void join() 方法 :等待該線程終止
void join(long millis) 方法 :等待該線程終止的時間最長為millis毫秒
需求: 當主線程運行到20的時候(i =20)的時候,讓JoinThread線程加進來直到執行完成,在執行主線程.
1 package com.wfd360.thread; 2 3 import org.junit.Test; 4 5 /** 6 * @author 姿勢帝-博客園 7 * @address https://www.cnblogs.com/newAndHui/ 8 * @WeChat 851298348 9 * @create 05/04 6:31 10 * @description 11 */ 12 public class Test05Join { 13 /** 14 * 需求: 15 * 當主線程for迴圈到i=20時,等JoinThread線程執行完成後,在執行for迴圈的線程 16 * @throws InterruptedException 17 */ 18 @Test 19 public void testJoinThread() throws InterruptedException { 20 System.out.println("---test start-------"); 21 // 開啟線程 22 JoinThread thread = new JoinThread(); 23 thread.start(); 24 // 迴圈列印線程 25 for (int i = 0; i < 100; i++) { 26 System.out.println("======testJoinThread=========="+i); 27 Thread.sleep(1); 28 if (i==20){ 29 // 等線程JoinThread執行完成 30 thread.join(); 31 } 32 } 33 System.out.println("=============等待線程執行完成==================="); 34 Thread.sleep(10*1000); 35 System.out.println("---test end-------"); 36 } 37 38 class JoinThread extends Thread { 39 @Override 40 public void run() { 41 for (int i = 0; i < 100; i++) { 42 System.out.println("=====JoinThread=======" + i); 43 // 模擬處理很多業務耗時1毫秒 44 try { 45 Thread.sleep(1); 46 } catch (InterruptedException e) { 47 e.printStackTrace(); 48 } 49 } 50 } 51 } 52 }Test05Join
1.7.線程優先順序
直接上代碼
1 package com.wfd360.thread; 2 3 /** 4 * @author 姿勢帝-博客園 5 * @address https://www.cnblogs.com/newAndHui/ 6 * @WeChat 851298348 7 * @create 05/05 8:17 8 * @description <p> 9 * 1.==>線程優先順序的理解: 10 * 線程的優先順序和生活中類似,高優先順序線程的執行優先於低優先順序線程; 11 * 並不是絕對的,可能優先順序高的線程優先 比 優先順序低的線程先執行,只能說,高優先順序的線程優先執行的幾率更多; 12 * (比如兩個線程,一個優先順序高,一個優先順序低,如果一共運行一個小時,優先順序高的線程執行遠遠大於優先順序低的但是並不是說優先順序高的先執行完, 13 * 在執行優先順序低的) 14 * 2.==>重新設置線程優先順序 15 * int getPriority() 返回線程的優先順序。 16 * void setPriority(int newPriority) 更改線程的優先順序。Java線程的優先順序從1到10級別,值越大優先順序越高. 17 * 3.==>線程的預設優先順序受創建線程的環境影響,預設值5,自定義線程的預設優先順序和創建它的環境的線程優先順序一致 18 * </p> 19 */ 20 public class Test06Priority { 21 /** 22 * 測試獲取線程優先順序,設置線程優先順序,驗證線程優先順序受創建環境影響 23 * @param args 24 */ 25 public static void main(String[] args) { 26 Thread threadMain = Thread.currentThread(); 27 // 獲取預設優先順序數字 28 System.out.println("main線程預設優先順序:" +threadMain.getPriority());// 5 29 // 重新設置預設優先順序數字 30 threadMain.setPriority(8); 31 // 再次重新獲取優先順序數字 32 System.out.println("main線程修改後的優先順序:" +threadMain.getPriority());// 8 33 // 創建一個線程查看優先順序 34 Thread thread = new Thread(); 35 System.out.println("thread線程的優先順序:" +thread.getPriority());// 8 受創建環境影響 36 } 37 }
1.8.後臺線程,即守護線程
直接看代碼
1 package com.wfd360.thread; 2 3 import com.wfd360.thread.demo04Daemon.DaemonThreaad; 4 5 /** 6 * @author 姿勢帝-博客園 7 * @address https://www.cnblogs.com/newAndHui/ 8 * @WeChat 851298348 9 * @create 05/05 9:12 10 * @description <p> 11 * 後臺線程,即守護線程 12 * 後臺線程:指為其他線程提供服務的線程,也稱為守護線程。JVM的垃圾回收線程就是一個後臺線程。 13 * 需求:嘗試把線程標記為後臺線程或者標記為(前臺)線程; 14 * Thread類提供的方法: 15 * 方法1: void setDaemon(boolean on) 將該線程標記為守護線程或用戶線程,true為後臺線程,false為用戶線程(前臺線下) 16 * 怎樣測試該線程是否是守護線程? 17 * 方法2:isDaemon() 測試該線程是否為守護線程. true為後臺線程,false為用戶線程(前臺線下) 18 * <p> 19 * 結論1:活動的線程(已經在執行的線程t.start())不能設置後臺線程,即主線程不能設置為後臺線程。 20 * 結論2: 自定義線程的預設狀態和環境有關,後臺線程中創建的線程預設是後臺線程,前臺線程中創建的線程為前臺線程. 21 * 結論3: 前臺線程執行完後,會直接關閉後臺線程,即自定義的後臺線程不一定能執行完成 22 * </p> 23 */ 24 public class Test07Daemon { 25 /** 26 * 測試1 27 * 查看主線程的狀態,嘗試更改 28 * 結論:活動的線程不能設置為後臺線程 29 * 30 * @param args 31 */ 32 public static void main1(String[] args) { 33 Thread threadMain = Thread.currentThread(); 34 System.out.println("是後臺線程麽:" + threadMain.isDaemon());// false 35 threadMain.setDaemon(true); // 報錯,活動的線程不能設置為後臺線程 36 System.out.println("修改後是後臺線程麽:" + threadMain.isDaemon()); 37 } 38 39 /** 40 * 測試2 41 * 查看主線程中 創建線程的狀態,嘗試更改; 42 * 43 * @param args 44 */ 45 public static void main2(String[] args) { 46 Thread thread = new Thread(); 47 // false 48 System.out.println("是後臺線程麽:" + thread.isDaemon()); 49 // 修改為後臺線程 50 thread.setDaemon(true); 51 System.out.println("修改後是後臺線程麽:" + thread.isDaemon()); 52 }