概述
- Java 給多線程編程提供了內置的支持。 一條線程指的是進程中一個單一順序的控制流,一個進程中可以並發多個線程,每條線程並行執行不同的任務。
- 多線程是多任務的一種特別的形式,但多線程使用了更小的資源開銷。
- 這里定義和線程相關的另一個術語 - 進程:一個進程包括由操作系統分配的內存空間,包含一個或多個線程。一個線程不能獨立的存在,它必須是進程的一部分。一個進程一直運行,直到所有的非守護線程都結束運行后才能結束。
- 多線程能滿足程序員編寫高效率的程序來達到充分利用 CPU 的目的。
大白話:
- 通常在一個進程中可以包含若干個線程,當然一個進程中至少有一個線程,不然沒有存在的意義。線程式CPU調度和執行的單位
- 說起進程,就不得不說下程序。程序是指令和數據的有序集合,其本身沒有任何運行的含義,是一個靜態的概念
- 而進程則是執行程序的一次執行過程,它是一個動態的概念。是系統資源分配的單位
- 注意:很多多線程是模擬出來的,真正的多線程是指有多個CPU,即多核,如服務器。如果是模擬出來的多線程,即在一個cpu的情況下,在同一時間點,cpu只能執行一個代碼,因為切換的很快,所以就有同時執行的錯覺
知識點:
- 線程就是獨立的執行路徑
- 在程序運行時,即使沒有自己創建線程,后台也會有多個線程,如主線程main(),gc()垃圾回收線程;
- main()稱之為主線程,為系統的入口,用於執行整個程序
- 在一個進程中,如果開辟了多個線程,線程的運行由調度器安排執行,調度器是與操作系統緊密相關的,先后順序是不能人為的干預的
- 對同一份資源操作時,會存在資源搶奪的問題,需要加入並發控制
- 線程會帶來額外的開銷,如cpu調度事件,並發控制開銷
- 每個線程在自己的工作內存交互,內存控制不當會造成數據不一致
一個線程的生命周期
線程是一個動態執行的過程,它也有一個從產生到死亡的過程。
下圖顯示了一個線程完整的生命周期。
- 新建狀態:
使用 new 關鍵字和 Thread 類或其子類建立一個線程對象后,該線程對象就處於新建狀態。它保持這個狀態直到程序 start() 這個線程。
- 就緒狀態:
當線程對象調用了start()方法之后,該線程就進入就緒狀態。就緒狀態的線程處於就緒隊列中,要等待JVM里線程調度器的調度。
- 運行狀態:
如果就緒狀態的線程獲取 CPU 資源,就可以執行 run(),此時線程便處於運行狀態。處於運行狀態的線程最為復雜,它可以變為阻塞狀態、就緒狀態和死亡狀態。
- 阻塞狀態:
如果一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所占用資源之后,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或獲得設備資源后可以重新進入就緒狀態。可以分為三種:
等待阻塞:運行狀態中的線程執行 wait() 方法,使線程進入到等待阻塞狀態。
同步阻塞:線程在獲取 synchronized 同步鎖失敗(因為同步鎖被其他線程占用)。
其他阻塞:通過調用線程的 sleep() 或 join() 發出了 I/O 請求時,線程就會進入到阻塞狀態。當sleep() 狀態超時,join() 等待線程終止或超時,或者 I/O 處理完畢,線程重新轉入就緒狀態。
- 終止狀態:
一個運行狀態的線程完成任務或者其他終止條件發生時,該線程就切換到終止狀態。
線程的優先級
- 每一個 Java 線程都有一個優先級,這樣有助於操作系統確定線程的調度順序。
- 線程開啟不一定立即執行,由cpu調度
- Java 線程的優先級是一個整數,其取值范圍是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
- 默認情況下,每一個線程都會分配一個優先級 NORM_PRIORITY(5)。
- 具有較高優先級的線程對程序更重要,並且應該在低優先級的線程之前分配處理器資源。但是,線程優先級不能保證線程執行的順序,而且非常依賴於平台。
- 在下面的線程狀態節點具體講述
創建一個線程
Java 提供了三種創建線程的方法:
- 通過繼承 Thread 類本身;(重點)
- 通過實現 Runnable 接口;(重點)
- 通過 Callable 和 Future 創建線程;(重點)
繼承 Thread 類創建線程
- 創建一個線程的第一種方法是創建一個新的類,該類繼承 Thread 類,然后創建一個該類的實例。
- 繼承類必須重寫 run() 方法,該方法是新線程的入口點。它也必須調用 start() 方法才能執行。
- 該方法盡管被列為一種多線程實現方式,但是本質上也是實現了 Runnable 接口的一個實例。
1 //創建線程方式一:繼承Thread類,重寫run()方法,調用start開啟線程 2 public class TestThread01 extends Thread { 3 @Override 4 public void run() { 5 //run方法線程體 6 for (int i = 0; i < 3; i++) { 7 System.out.println("好好學習"); 8 } 9 } 10 11 public static void main(String[] args) { 12 //main線程,主線程 13 14 //創建一個線程對象 15 TestThread01 testThread01 = new TestThread01(); 16 17 //testThread01.run();執行run方法,沒有開啟線程,依舊是自上而下執行代碼 18 19 //調用start()方法,開啟線程,run方法體線程和main()主線程根據調度交叉執行 20 testThread01.start(); 21 for (int i = 0; i <3 ; i++) { 22 System.out.println("天天向上"); 23 } 24 } 25 }Thread線程練習-網圖下載
前置條件:
- 下載Commons IO包,下載地址:http://commons.apache.org/proper/commons-io/download_io.cgi
- 下載好之后放到項目的lib目錄下,沒有lib目錄的,自己新增一個。
- 右鍵lib目錄,選擇Add as Library,點擊OK即可
1 //練習Thread,實現多線程下載圖片 2 public class TestThread2 extends Thread{ 3 private String url; 4 private String name; 5 6 public TestThread2(String url,String name){ 7 this.url=url; 8 this.name=name; 9 } 10 11 //下載圖片線程的執行體 12 @Override 13 public void run() { 14 WebDownloader webDownloader = new WebDownloader(); 15 webDownloader.downloader(url,name); 16 System.out.println("下載了文件名為:"+name); 17 } 18 19 public static void main(String[] args) { 20 TestThread2 t1 = new TestThread2("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度圖片-1.jpg"); 21 TestThread2 t2 = new TestThread2("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度圖片-2.jpg"); 22 TestThread2 t3 = new TestThread2("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度圖片-3.jpg"); 23 24 t1.start(); 25 t2.start(); 26 t3.start(); 27 } 28 } 29 30 //下載器 31 class WebDownloader{ 32 //下載方法 33 public void downloader(String url,String name){ 34 try { 35 FileUtils.copyURLToFile(new URL(url),new File(name)); 36 } catch (IOException e) { 37 e.printStackTrace(); 38 System.out.println("IO異常,download方法出現問題"); 39 } 40 } 41 }
實現 Runnable 接口創建線程
創建一個線程,最簡單的方法是創建一個實現 Runnable 接口的類。
為了實現 Runnable,一個類只需要執行一個方法調用 run(),聲明如下:
public void run()可以重寫該方法,重要的是理解的 run() 可以調用其他方法,使用其他類,並聲明變量,就像主線程一樣。
在創建一個實現 Runnable 接口的類之后,你可以在類中實例化一個線程對象。
Thread 定義了幾個構造方法,下面的這個是我們經常使用的:
Thread(Runnable threadOb,String threadName);這里,threadOb 是一個實現 Runnable 接口的類的實例,並且 threadName 指定新線程的名字。
新線程創建之后,你調用它的 start() 方法它才會運行。
void start();
1 //創建線程方式二:實現runnable接口,重寫run方法,執行線程需要丟入runnable接口實現類,調用start方法啟動線程 2 public class TestThread03 implements Runnable{ 3 @Override 4 public void run() { 5 for (int i = 0; i < 200; i++) { 6 System.out.println("好好學習"+i); 7 } 8 } 9 10 public static void main(String[] args) { 11 //創建runnable接口的實現類對象 12 TestThread03 testThread03 = new TestThread03(); 13 //創建線程對象,通過線程對象來開啟我們的線程,代理 14 new Thread(testThread03).start(); 15 16 for (int i = 0; i < 200; i++) { 17 System.out.println("天天向上"+i); 18 } 19 } 20 }Runnable 接口創建線程練習-下載網圖
1 public class TestThread04 implements Runnable{ 2 3 private String url; 4 private String name; 5 6 public TestThread04(String url,String name){ 7 this.url=url; 8 this.name= name; 9 } 10 11 @Override 12 public void run() { 13 new WebDownLoad01(url,name); 14 System.out.println("下載了文件名:"+name); 15 } 16 17 public static void main(String[] args) { 18 TestThread04 t1 = new TestThread04("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度圖片-1.jpg"); 19 TestThread04 t2 = new TestThread04("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度圖片-2.jpg"); 20 TestThread04 t3 = new TestThread04("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度圖片-3.jpg"); 21 22 new Thread(t1).start(); 23 new Thread(t2).start(); 24 new Thread(t3).start(); 25 } 26 } 27 //下載器 28 class WebDownLoad01{ 29 public WebDownLoad01(String url,String name){ 30 try { 31 FileUtils.copyURLToFile(new URL(url),new File(name)); 32 } catch (IOException e) { 33 e.printStackTrace(); 34 System.out.println("IO異常,下載失敗"); 35 } 36 } 37 }Runnable 接口創建線程練習-多個線程同時操作同一個對象
買火車票的例子
1 //多個線程同時操作同一個對象 2 //買火車票的例子 3 public class TestThread05 implements Runnable{ 4 5 //站台總票數 6 private int ticketNums=10; 7 8 @Override 9 public void run() { 10 while (true){ 11 //沒有票時退出 12 if(ticketNums<=0){ 13 break; 14 } 15 16 //模擬延時 17 try { 18 Thread.sleep(200); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 23 System.out.println(Thread.currentThread().getName()+"-->拿到了第:"+ticketNums--+"票"); //Thread.currentThread().getName()獲得當前執行線程的名字 24 } 25 } 26 27 public static void main(String[] args) { 28 TestThread05 testThread05 = new TestThread05(); 29 30 new Thread(testThread05,"小明").start();//小明:線程的名字 31 new Thread(testThread05,"小張").start(); 32 new Thread(testThread05,"黃牛").start(); 33 } 34 }輸出結果,發現同一張票被兩個人買走了,發現了線程的問題:多個線程操作同一個資源的時候,線程不安全,數據紊亂,后面線程幾個重要概念中的線程安全會有具體講述
Runnable 接口創建線程練習-龜兔賽跑
1 //模擬龜兔賽跑 2 public class Race implements Runnable{ 3 4 // 勝利者 5 private static String winner; 6 7 //線程運行體 8 @Override 9 public void run() { 10 for (int i = 0; i <= 100; i++) { 11 //判斷比賽是否結束 12 boolean b = gameOver(i); 13 //如果有勝利者了,就停止程序 14 if (b){ 15 break; 16 } 17 System.out.println(Thread.currentThread().getName()+"跑了"+i+"米"); 18 } 19 } 20 21 //判斷是否完成比賽 22 private boolean gameOver(int steps){ 23 //判斷是否有勝利者 24 if (winner!=null){ 25 return true; 26 }{ 27 if (steps>=100) { 28 winner=Thread.currentThread().getName(); 29 System.out.println("winner is :"+winner); 30 return true; 31 } 32 } 33 return false; 34 } 35 36 public static void main(String[] args) { 37 Race race = new Race(); 38 39 new Thread(race,"烏龜").start(); 40 new Thread(race,"兔子").start(); 41 } 42 }
實現Callable接口創建線程
了解即可
- 實現Callable接口,需要返回值類型;
- 重寫call方法,需要拋出異常
- 創建目標對象
- 創建執行服務ExecutorService ser = Executors.newFixedThreadPool();
- 提交執行Future<Boolean> r1 = ser.submit(t1);
- 獲得結果Boolean rs1 = r1.get();
- 關閉服務ser.shutdown();
1 //線程創建方式三:實現Callable接口 2 public class TestCallable implements Callable { 3 private String url; 4 private String name; 5 6 public TestCallable(String url,String name){ 7 this.url=url; 8 this.name=name; 9 } 10 11 @Override 12 public Object call() throws Exception { 13 WebDownload webDownload = new WebDownload(url,name); 14 System.out.println(name+"文件已下載"); 15 return true; 16 } 17 18 public static void main(String[] args) throws ExecutionException, InterruptedException { 19 TestCallable t1 = new TestCallable("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度圖片-1.jpg"); 20 TestCallable t2 = new TestCallable("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度圖片-2.jpg"); 21 TestCallable t3 = new TestCallable("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度圖片-3.jpg"); 22 23 //創建執行服務 24 ExecutorService ser = Executors.newFixedThreadPool(3); 25 26 //提交執行 27 Future<Boolean> r1 = ser.submit(t1); 28 Future<Boolean> r2 = ser.submit(t2); 29 Future<Boolean> r3 = ser.submit(t3); 30 31 //獲取結果 32 Boolean rs1 = r1.get(); 33 Boolean rs2 = r2.get(); 34 Boolean rs3 = r3.get(); 35 36 //關閉服務 37 ser.shutdown(); 38 } 39 } 40 41 //下載器 42 class WebDownload{ 43 public WebDownload(String url,String name){ 44 try { 45 FileUtils.copyURLToFile(new URL(url),new File(name)); 46 } catch (IOException e) { 47 e.printStackTrace(); 48 System.out.println("IO異常,下載失敗"); 49 } 50 } 51 }
創建線程的三種方式的對比
采用實現 Runnable、Callable 接口的方式創建多線程時,線程類只是實現了 Runnable 接口或 Callable 接口,還可以繼承其他類。
使用繼承 Thread 類的方式創建多線程時,編寫簡單,如果需要訪問當前線程,則無需使用 Thread.currentThread() 方法,直接使用 this 即可獲得當前線程。
拓展
靜態代理
- 真實對象和代理對象都要實現同一個接口
- 代理對象要代理真實角色
- #好處
- 代理對象可以做很多真實對象做不了的事情
- 真實對象專注做自己的事情
1 //以結婚找婚慶公司舉例 2 public class StaticProxy { 3 public static void main(String[] args) { 4 WeddingCompany weddingCompany = new WeddingCompany(new You()); 5 weddingCompany.HappyMarry(); 6 } 7 } 8 9 //同一個接口———結婚這件事情 10 interface Marry{ 11 void HappyMarry(); 12 } 13 14 //真實對象————結婚的主人公 15 class You implements Marry{ 16 @Override 17 public void HappyMarry() { 18 System.out.println("張三要結婚了"); 19 } 20 } 21 22 //代理對象————結婚場景布置找的婚慶公司,代理角色 23 class WeddingCompany implements Marry{ 24 25 //代理的主人公,結婚的主人公,給誰幫忙的 26 private Marry target; 27 28 public WeddingCompany(Marry target){ 29 this.target= target; 30 } 31 32 @Override 33 public void HappyMarry() { 34 before(); 35 this.target.HappyMarry(); 36 after(); 37 } 38 39 private void before() { 40 System.out.println("結婚之前布置現場"); 41 } 42 43 private void after() { 44 System.out.println("結婚之后收尾款"); 45 } 46 47 48 }
- 反觀線程Thread和Runable,Thread類其實是實現Runable接口的,Thread類相當於就是一個靜態代理,完成Runable許多完成不了的事情,比如使用Runable創建線程,最后運行線程使用的是代理Thread的start方法;Runnabe接口啟動線程就是是用了靜態代理的方式
- Runnable接口和Thread代理都有run方法,最后調用的是Thread的start方法,但實際執行的還是Runnable中的run方法中的方法體
Lambda表達式
好處:
- 避免匿名內部類定義過多
- 其實質屬於函數式編程的概念
- 例子:new Thread(()-> System.out.println("多線程學習")).start();
函數式接口:
- 任何接口,如果只包含唯一一個抽象方法,那么它就是一個函數式接口,比如
- 對於函數式接口,我們可以通過lambda表達式來創建該接口的對象
推導lambda表達式
正常寫法
1 /* 2 推導lambda表達式--正常寫法 3 */ 4 public class TestLambda01 { 5 public static void main(String[] args) { 6 ILike like = new Like(); 7 like.lambda(); 8 } 9 } 10 11 //1.定義一個函數式接口 12 interface ILike{ 13 void lambda(); 14 } 15 16 //2.實現類 17 class Like implements ILike{ 18 @Override 19 public void lambda() { 20 System.out.println("i like lambda"); 21 } 22 }靜態內部類
1 /* 2 推導lambda表達式--優化方法:靜態內部類 3 */ 4 public class TestLambda01 { 5 6 public static void main(String[] args) { 7 ILike like = new Like(); 8 like.lambda(); 9 10 like = new Like1(); 11 like.lambda(); 12 } 13 14 //3.靜態內部類 15 static class Like1 implements ILike{ 16 @Override 17 public void lambda() { 18 System.out.println("i like lambda1-靜態內部類"); 19 } 20 } 21 } 22 23 //1.定義一個函數式接口 24 interface ILike{ 25 void lambda(); 26 } 27 28 //2.實現類 29 class Like implements ILike{ 30 @Override 31 public void lambda() { 32 System.out.println("i like lambda"); 33 } 34 }局部內部類
1 /* 2 推導lambda表達式--優化方法:局部內部類 3 */ 4 public class TestLambda01 { 5 6 public static void main(String[] args) { 7 ILike like = new Like(); 8 like.lambda(); 9 10 //3.靜態內部類 11 like = new Like1(); 12 like.lambda(); 13 14 //4.局部內部類 15 class Like2 implements ILike{ 16 @Override 17 public void lambda() { 18 System.out.println("i like lambda2-局部內部類"); 19 } 20 } 21 22 //4.局部內部類 23 like = new Like2(); 24 like.lambda(); 25 } 26 27 //3.靜態內部類 28 static class Like1 implements ILike{ 29 @Override 30 public void lambda() { 31 System.out.println("i like lambda1-靜態內部類"); 32 } 33 } 34 } 35 36 //1.定義一個函數式接口 37 interface ILike{ 38 void lambda(); 39 } 40 41 //2.實現類 42 class Like implements ILike{ 43 @Override 44 public void lambda() { 45 System.out.println("i like lambda"); 46 } 47 }匿名內部類
1 /* 2 推導lambda表達式--優化方法:匿名內部類 3 */ 4 public class TestLambda01 { 5 6 public static void main(String[] args) { 7 ILike like = new Like(); 8 like.lambda(); 9 10 //3.靜態內部類 11 like = new Like1(); 12 like.lambda(); 13 14 //4.局部內部類 15 class Like2 implements ILike{ 16 @Override 17 public void lambda() { 18 System.out.println("i like lambda2-局部內部類"); 19 } 20 } 21 22 //4.局部內部類 23 like = new Like2(); 24 like.lambda(); 25 26 //5.匿名內部類,沒有類的名稱,必須借助接口或者父類 27 like = new ILike() { 28 @Override 29 public void lambda() { 30 System.out.println("i like lambda3-匿名內部類"); 31 } 32 }; 33 //5.匿名內部類 34 like.lambda(); 35 } 36 37 //3.靜態內部類 38 static class Like1 implements ILike{ 39 @Override 40 public void lambda() { 41 System.out.println("i like lambda1-靜態內部類"); 42 } 43 } 44 } 45 46 //1.定義一個函數式接口 47 interface ILike{ 48 void lambda(); 49 } 50 51 //2.實現類 52 class Like implements ILike{ 53 @Override 54 public void lambda() { 55 System.out.println("i like lambda"); 56 } 57 }思考:怎么還能將它再簡化呢,於是乎,JDK1.8出了個lambda表達式
1 /* 2 推導lambda表達式--優化方法:lambda表達式 3 */ 4 public class TestLambda01 { 5 6 public static void main(String[] args) { 7 ILike like = new Like(); 8 like.lambda(); 9 10 //3.靜態內部類 11 like = new Like1(); 12 like.lambda(); 13 14 //4.局部內部類 15 class Like2 implements ILike{ 16 @Override 17 public void lambda() { 18 System.out.println("i like lambda2-局部內部類"); 19 } 20 } 21 22 //4.局部內部類 23 like = new Like2(); 24 like.lambda(); 25 26 //5.匿名內部類,沒有類的名稱,必須借助接口或者父類 27 like = new ILike() { 28 @Override 29 public void lambda() { 30 System.out.println("i like lambda3-匿名內部類"); 31 } 32 }; 33 //5.匿名內部類 34 like.lambda(); 35 36 //6.lambda簡化 37 like = ()->{ 38 System.out.println("i like lambda4-lambda簡化"); 39 }; 40 //6.lambda簡化 41 like.lambda(); 42 } 43 44 //3.靜態內部類 45 static class Like1 implements ILike{ 46 @Override 47 public void lambda() { 48 System.out.println("i like lambda1-靜態內部類"); 49 } 50 } 51 } 52 53 //1.定義一個函數式接口 54 interface ILike{ 55 void lambda(); 56 } 57 58 //2.實現類 59 class Like implements ILike{ 60 @Override 61 public void lambda() { 62 System.out.println("i like lambda"); 63 } 64 }lambda例子
1 //例子-帶參數的lambda 2 public class TestLambda02 { 3 public static void main(String[] args) { 4 5 LunchEat lunchEat = (String name)->{ 6 System.out.println("中午吃"+name); 7 }; 8 9 lunchEat.eat("牛肉"); 10 } 11 } 12 13 interface LunchEat { 14 void eat(String name); 15 }疑問:上述的lambda還能不能再簡化?可以,請看下面代碼👇
1 //例子-帶參數的lambda 2 public class TestLambda02 { 3 public static void main(String[] args) { 4 5 LunchEat lunchEat=null; 6 7 //未簡化 8 lunchEat = (String name)->{ 9 System.out.println("中午吃"+name); 10 }; 11 12 //簡化1:去掉參數類型 13 lunchEat = (name)->{ 14 System.out.println("早上吃"+name); 15 }; 16 17 //簡化2:去掉括號() 18 lunchEat=name->{ 19 System.out.println("晚上吃"+name); 20 }; 21 22 //簡化3:去掉花括號() 23 lunchEat=name->System.out.println("夜宵吃"+name); 24 25 lunchEat.eat("牛肉"); 26 } 27 } 28 29 interface LunchEat { 30 void eat(String name); 31 }lambda簡化總結
- lambda表達式只有在代碼只有一行的情況下,才能簡化成一行,如果多行,那么就用代碼塊包裹
- 前提是接口為函數式接口
- 多個參數也可以去掉參數類型,要去掉就都去掉,但是必須得加上括號
線程狀態
線程停止
- 建議線程正常停止—-->利用次數,不建議死循環
- 建議使用標志位----->設置一個標志位
- 不要使用stop或者destroy等過時或者JDK不建議使用的方法
1 //線程停止 2 /* 3 建議線程正常停止—-->利用次數,不建議死循環 4 建議使用標志位----->設置一個標志位 5 不要使用stop或者destroy等過時或者JDK不建議使用的方法 6 */ 7 public class TestStop implements Runnable{ 8 9 //1.設置一個標識位 10 private boolean flag = true; 11 12 13 @Override 14 public void run() { 15 int i=0; 16 while (flag){ 17 System.out.println("thread is running"+i++); 18 } 19 20 } 21 22 //2.設置一個公開的方法停止線程,轉換標志位 23 public void stop(){ 24 this.flag = false; 25 } 26 27 public static void main(String[] args) { 28 29 TestStop testStop = new TestStop(); 30 new Thread(testStop).start(); 31 32 for (int i = 0; i < 30; i++) { 33 //main()自己的線程 34 System.out.println("main is running"+i++); 35 if (i==9){ 36 //調用stop方法切換標志位,讓線程停止 37 testStop.stop(); 38 System.out.println("thread線程該停止了"); 39 } 40 } 41 42 } 43 }
線程休眠
- Thread.sleep()
- sleep(時間)指定當前線程阻塞的毫秒數
- sleep存在異常InterruptedException
- sleep時間達到后線程進入就緒狀態
- sleep可以模擬網絡延時,將線程的問題暴露出來,同時還可以模擬倒計時等
- 每一個對象都有一個鎖,sleep不會釋放鎖
1 //模擬網絡延時 2 public class TestSleep implements Runnable{ 3 //站台總票數 4 private int ticketNums=10; 5 6 @Override 7 public void run() { 8 while (true){ 9 //沒有票時退出 10 if(ticketNums<=0){ 11 break; 12 } 13 14 //模擬延時 15 try { 16 Thread.sleep(200); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 21 System.out.println(Thread.currentThread().getName()+"-->拿到了第:"+ticketNums--+"票"); //Thread.currentThread().getName()獲得當前執行線程的名字 22 } 23 } 24 25 public static void main(String[] args) { 26 TestSleep testSleep = new TestSleep(); 27 28 new Thread(testSleep,"小明").start();//小明:線程的名字 29 new Thread(testSleep,"小張").start(); 30 new Thread(testSleep,"黃牛").start(); 31 } 32 }1 //倒計時 2 public class TestSleep02 { 3 public static void main(String[] args) { 4 try { 5 new TestSleep02().tenDown(); 6 } catch (InterruptedException e) { 7 e.printStackTrace(); 8 } 9 10 } 11 12 public static void tenDown() throws InterruptedException{ 13 int num=10; 14 while (true){ 15 Thread.sleep(1000); 16 System.out.println(num--); 17 if (num<=0){ 18 break; 19 } 20 } 21 } 22 }1 //打印系統當前時間 2 public class TestSleep02 { 3 public static void main(String[] args) throws InterruptedException{ 4 //打印系統當前時間 5 Date startTime= new Date(System.currentTimeMillis());//獲得系統當前時間 6 while (true){ 7 Thread.sleep(1000); 8 System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime)); 9 startTime=new Date(System.currentTimeMillis());//更新時間 10 } 11 } 12 }
線程禮讓
- Thread.yield()
- 禮讓線程,讓當前正在執行的線程暫停,但不阻塞
- 將線程從運行狀態轉換為就緒狀態
- 讓CPU重新調度,禮讓不一定成功!看CPU心情
1 //測試禮讓線程 2 //禮讓不一定成功,看CPU調度 3 public class TestYield { 4 public static void main(String[] args) { 5 MyYield myYield = new MyYield(); 6 7 new Thread(myYield,"a").start(); 8 new Thread(myYield,"b").start(); 9 } 10 } 11 12 class MyYield implements Runnable{ 13 14 @Override 15 public void run() { 16 System.out.println(Thread.currentThread().getName()+"線程開始執行"); 17 Thread.yield(); //禮讓 18 System.out.println(Thread.currentThread().getName()+"線程停止執行"); 19 } 20 }
線程強制執行
- thread.join()
- Join合並線程,待此線程執行完成后,再執行其他線程,其他線程阻塞
- 可以想象成插隊
1 //線程強制執行,想象為插隊 2 public class TestJoin implements Runnable{ 3 public static void main(String[] args) throws InterruptedException { 4 //我們的線程 5 TestJoin testJoin = new TestJoin(); 6 Thread thread = new Thread(testJoin); 7 thread.start(); 8 9 //main線程 10 for (int i = 0; i < 50; i++) { 11 System.out.println("main線程"); 12 if (i==10){ 13 thread.join();//原本main和我們自己的線程是根據cpu調度並行執行的,但是當i=10時,main線程停下來不走,等vip線程執行完 14 } 15 } 16 17 } 18 19 @Override 20 public void run() { 21 //我們的線程 22 for (int i = 0; i <30 ; i++) { 23 System.out.println("線程VIP"); 24 } 25 } 26 }
線程狀態觀測
thread.getState()
1 //觀察線程的狀態 2 public class TestState { 3 public static void main(String[] args) throws InterruptedException { 4 //lambda表達式重寫Runable的run方法,給thread實現類執行 5 Thread thread = new Thread(()->{ 6 for (int i = 0; i < 5; i++) { 7 try { 8 Thread.sleep(1000); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 } 13 System.out.println("/////"); 14 }); 15 //上面等價於下面這個寫法 16 /* 17 Runnable runnable = ()->{ 18 System.out.println(""); 19 }; 20 21 Thread thread =new Thread(runnable); 22 */ 23 24 //觀察裝填 25 Thread.State state = thread.getState(); 26 System.out.println(state); //new 27 28 //觀察啟動后 29 thread.start(); 30 Thread.State state1 = thread.getState(); 31 System.out.println(state); 32 33 while (state != Thread.State.TERMINATED){ //只要線程不終止,就一致輸出狀態 34 Thread.sleep(100); 35 state=thread.getState();//更新狀態 36 System.out.println(state); 37 } 38 39 //thread.start()報錯,死亡后的線程,不能再啟動 40 41 } 42 }線程死亡后不能再啟動
線程的優先級
thread.setPriority()
1 public class TestPriority { 2 public static void main(String[] args) { 3 //main線程的優先級 4 System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority()); 5 6 //我們自己的線程 7 MyPriority myPriority = new MyPriority(); 8 9 10 Thread t1 = new Thread(myPriority); 11 Thread t2 = new Thread(myPriority); 12 Thread t3 = new Thread(myPriority); 13 Thread t4 = new Thread(myPriority); 14 Thread t5 = new Thread(myPriority); 15 16 //設置優先級再啟動 17 t1.setPriority(3); 18 t1.start(); 19 20 /* 21 優先級范圍為1-10,數值高,優先級高,下面兩個線程執行會報錯 22 t2.setPriority(11); 23 t2.start(); 24 25 t3.setPriority(-1); 26 t3.start(); 27 */ 28 29 t4.setPriority(Thread.MAX_PRIORITY);//最高優先級,相當於t4.setPriority(10) 30 t4.start(); 31 32 t5.setPriority(Thread.MIN_PRIORITY);//最低優先級,相當於t5.setPriority(1) 33 t5.start(); 34 } 35 } 36 37 class MyPriority implements Runnable{ 38 39 @Override 40 public void run() { 41 System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority()); 42 } 43 }總結
- 優先級的設定建議在start()調度前
- 優先級低只意味着獲得調度的概率低,並不是優先級低的就不會被調用。都得看cpu的調度
- 優先級低的先執行,我們成為“性能倒置”
守護線程
- thread.setDaemon(true)
- 線程分為:用戶線程和守護線程
- 虛擬機必須確保用戶線程執行完畢
- 虛擬機不用等待守護線程執行完畢
- 如:后台記錄操作日志、監控內存、垃圾回收等待...
1 //測試守護線程 2 //守護線程設置死循環,用戶線程為有限次數循環,用戶線程結束,虛擬機自動結束,不管守護線程有沒有運行完 3 4 public class TestDaemon { 5 public static void main(String[] args) { 6 God god = new God(); 7 You you = new You(); 8 9 //將上帝設置為守護線程 10 Thread thread = new Thread(god); 11 thread.setDaemon(true); //setDaemon()默認為false,false表示的是用戶線程,正常線程都是用戶線程;為true代表守護線程 12 13 thread.start(); //守護線程啟動 14 15 new Thread(you).start();//用戶線程啟動 16 } 17 } 18 19 //上帝,守護線程 20 class God implements Runnable{ 21 @Override 22 public void run() { 23 while (true){ 24 System.out.println("上帝永遠守護着你"); 25 } 26 } 27 } 28 29 //人,用戶線程 30 class You implements Runnable{ 31 @Override 32 public void run() { 33 for (int i = 0; i < 100; i++) { 34 System.out.println("每一年都要過的開心"); 35 } 36 System.out.println("=======goodbye!world======="); 37 } 38 }
線程同步
- 多個線程操作同一個資源
- 並發:同一個對象被多個線程同時操作
- 100張票被10萬人搶,100張票就是一個對象,10萬人就是多個線程
- 銀行卡里有100萬,兩個人同時取,一個在銀行櫃台取,一個使用手機銀行取
- 現實生活中,我們會遇到“同一個資源,多個人都想要使用”的問題,比如食堂排隊打飯,最天然的解決方法就是,排隊,一個一個來
- 處理多線程問題時,多個線程訪問同一個對象,並且某些線程還想修改這個對象。這時候我們就需要線程同步,線程同步其實就是一種等待機制,多個需要同時訪問此對象的線程進入這個對象的等待池形成隊列,等待前面線程使用完畢,下一個線程再使用
- 隊列+鎖才能保證線程同步的安全性
- 由於同一進程的多個線程共享同一塊存儲空間,在帶來方便的同時,也帶來了訪問沖突問題,為了保證數據在方法中被訪問時的正確性,在訪問時加入鎖機制synchronized。當一個線程獲得對象的排它鎖,獨占資源,其他線程必須等待,使用后釋放鎖即可,但是存在以下問題:
- 一個線程持有鎖會導致其他所有需要此鎖的線程掛起;
- 在多線程競爭下,加鎖,釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題;
- 如果一個優先級高的線程等待一個優先級低的線程釋放鎖,會導致優先級倒置,引發性能問題。
三大不安全案例
1 //不安全的買票 2 public class UnsafeBuyTicket { 3 public static void main(String[] args) { 4 BuyTicket buyTicket = new BuyTicket(); 5 new Thread(buyTicket,"小明").start(); 6 new Thread(buyTicket,"小王").start(); 7 new Thread(buyTicket,"小張").start(); 8 } 9 } 10 11 class BuyTicket implements Runnable{ 12 13 //票 14 private int ticketnums = 10; 15 16 //線程停止的標志位 17 boolean flag=true; 18 19 //線程運行的代碼塊 20 @Override 21 public void run() { 22 //買票 23 while (flag){ 24 try { 25 buy(); 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 } 29 } 30 } 31 32 //買票 33 private void buy() throws InterruptedException { 34 //判斷是否有票 35 if (ticketnums<=0){ 36 flag=false; 37 return; 38 } 39 40 //模擬延時 41 Thread.sleep(100); 42 System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketnums--); 43 } 44 }1 //不安全的取錢 2 //兩個人去銀行取錢 3 public class UnsafeBank { 4 public static void main(String[] args) { 5 Account account = new Account(100,"工資"); 6 7 Drawing xiaoming = new Drawing(account,50,"小明"); 8 Drawing xiaohong = new Drawing(account,100,"小紅"); 9 10 xiaoming.start(); 11 xiaohong.start(); 12 13 } 14 } 15 16 //賬戶 17 class Account{ 18 int money;//余額 19 String name;//卡名 20 21 public Account(int money,String name){ 22 this.money=money; 23 this.name=name; 24 } 25 } 26 27 //銀行:模擬取款 28 class Drawing extends Thread{ 29 Account account;//賬戶 30 int drawingMoney;//取了多少錢 31 int nowMoney;//現在手里有多少錢 32 33 public Drawing(Account account,int drawingMoney,String name){ 34 super(name); 35 this.account=account; 36 this.drawingMoney=drawingMoney; 37 } 38 39 //取錢 40 @Override 41 public void run() { 42 //判斷有沒有錢 43 if(account.money-drawingMoney<0){ 44 System.out.println(Thread.currentThread().getName()+"余額不足"); 45 return; 46 } 47 48 try { 49 Thread.sleep(1000); 50 } catch (InterruptedException e) { 51 e.printStackTrace(); 52 } 53 //卡內余額=余額-要取得錢 54 account.money=account.money-drawingMoney; 55 //你手里得錢 56 nowMoney=nowMoney+drawingMoney; 57 System.out.println(account.name+"余額為:"+account.money); 58 System.out.println(this.getName()+"手里的錢:"+nowMoney); 59 60 } 61 }1 //線程不安全的集合 2 public class UnsafeList { 3 public static void main(String[] args) { 4 List<String> list = new ArrayList<>(); 5 for (int i = 0; i < 10000; i++) { 6 new Thread(()->{ 7 list.add(Thread.currentThread().getName()); 8 }).start(); 9 } 10 System.out.println(list.size()); 11 } 12 }
同步方法及同步塊
同步方法
- 由於我們可以通過private關鍵字來保證數據對象只能被方法訪問,所以我們只需要針對方法提出一套機制,這套機制就是synchronized關鍵字,它包括兩種方法:synchronized方法和synchronized塊
- 同步方法:public synchronized void method(int args){}
- synchronized方法控制對“對象”的訪問,每個對象對應一把鎖,每個synchronized方法都必須獲得調用該方法的對象的鎖才能執行,否則線程會阻塞,方法一旦執行,就獨占該鎖,直到該方法返回才釋放鎖,后面堵塞的線程才能獲得這個鎖,繼續執行
- 缺陷:若將一個大的方法申名為synchronized將會影響效率
- 方法里面需要修改的內容才需要鎖,鎖太多,會浪費資源
同步塊
- 同步塊:synchronized(Obj){}
- Obj稱之為同步監視器
- Obj可以是任何對象,但是推薦使用共享資源作為同步監視器
- 同步方法中無需指定同步監視器,因為同步方法的同步監視器就是this,就是這個對象的本身,或者是class【后面反射筆記有】
- 同步監視器的執行過程
- 第一個線程訪問,鎖定同步監視器,執行其中代碼
- 第二個線程訪問,發現同步監視器被鎖定,無法訪問
- 第一個線程訪問完畢,解鎖同步監視器
- 第二個線程訪問,發現同步監視器沒有鎖,然后鎖定並訪問
1 //synchronized 同步方法,安全的買票 2 public class UnsafeBuyTicket { 3 public static void main(String[] args) { 4 BuyTicket buyTicket = new BuyTicket(); 5 new Thread(buyTicket,"小明").start(); 6 new Thread(buyTicket,"小王").start(); 7 new Thread(buyTicket,"小張").start(); 8 } 9 } 10 11 class BuyTicket implements Runnable{ 12 13 private int ticketnums = 10; 14 15 boolean flag=true; 16 17 @Override 18 public void run() { 19 while (flag){ 20 try { 21 buy(); 22 Thread.sleep(100); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 } 27 } 28 29 //synchronized 同步方法,鎖的是this,要鎖共享的資源對象 30 private synchronized void buy() throws InterruptedException { 31 if (ticketnums<=0){ 32 flag=false; 33 return; 34 } 35 36 System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketnums--); 37 } 38 }1 //同步塊鎖定synchronized(要鎖的對象){},安全的取錢 2 //兩個人去銀行取錢 3 public class UnsafeBank { 4 public static void main(String[] args) { 5 Account account = new Account(100,"工資"); 6 7 Drawing xiaoming = new Drawing(account,50,"小明"); 8 Drawing xiaohong = new Drawing(account,100,"小紅"); 9 10 xiaoming.start(); 11 xiaohong.start(); 12 13 } 14 } 15 16 //賬戶 17 class Account{ 18 int money;//余額 19 String name;//卡名 20 21 public Account(int money,String name){ 22 this.money=money; 23 this.name=name; 24 } 25 } 26 27 //銀行:模擬取款 28 class Drawing extends Thread{ 29 Account account;//賬戶 30 int drawingMoney;//取了多少錢 31 int nowMoney;//現在手里有多少錢 32 33 public Drawing(Account account,int drawingMoney,String name){ 34 super(name); 35 this.account=account; 36 this.drawingMoney=drawingMoney; 37 } 38 39 //取錢 40 @Override 41 public void run() { 42 //synchronized 同步塊鎖定,代碼放到同步塊里面,要鎖共享的資源對象account他們的銀行卡,並且鎖的要是變化的量 43 // 可以試一下,如果鎖放在銀行的方法Drawing上 class synchronized Drawing extends Thread{},是不成功的,因為銀行不是他們的共享資源,卡才是,卡的對象是account,鎖account 44 synchronized (account){ 45 if(account.money-drawingMoney<0){ 46 System.out.println(Thread.currentThread().getName()+"余額不足"); 47 return; 48 } 49 try { 50 Thread.sleep(1000); 51 } catch (InterruptedException e) { 52 e.printStackTrace(); 53 } 54 //卡內余額=余額-要取得錢 55 account.money=account.money-drawingMoney; 56 //你手里得錢 57 nowMoney=nowMoney+drawingMoney; 58 System.out.println(account.name+"余額為:"+account.money); 59 System.out.println(this.getName()+"手里的錢:"+nowMoney); 60 } 61 } 62 }//同步塊鎖,線程安全的集合 public class UnsafeList { public static void main(String[] args) throws InterruptedException { List<String> list = new ArrayList<>(); for (int i = 0; i < 100; i++) { new Thread(()->{ //同步塊鎖,避免寫入列表的時候寫入臟數據 synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); } Thread.sleep(1000);//不加時間,主線索跑完成,print已經答應出來,但是run線程還是沒有跑完,這個休眠是給主線程休眠的 System.out.println(list.size()); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } } }線程安全的集合,不要加同步塊鎖 CopyOnWriteArrayList
//拓展:測試JUC並發,安全類型的集合 public class TextJUC { public static void main(String[] args) throws InterruptedException { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList(); //線程安全的集合 for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(1000); System.out.println(list.size()); } }
死鎖
- 多個線程各自占有一些共享資源,並且互相等待其他線程占有的資源才能運行,而導致兩個或者多個線程都在等待對方釋放資源,都停止執行的情形。某一個同步塊同時擁有“兩個以上對象的鎖”時,就可能發生“死鎖”的問題
- 產生死鎖的四個必要條件:
- 互斥條件:一個資源每次只能被一個進程使用
- 請求與保持條件:一個進程因請求資源而阻塞時,對方已獲得的資源保持不放
- 不剝奪條件:進程已獲得的資源,在未使用完之前,不能強行剝奪
- 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系
- 上面列出了死鎖的四個必要條件,我們只要想辦法破其中的任意一個或多個條件就可以避免死鎖的發生
1 //死鎖:多個線程互相抱着對方需要的資源,形成僵持 2 public class DeadLock { 3 public static void main(String[] args) { 4 MakeUp m1 = new MakeUp(0,"灰姑娘"); 5 MakeUp m2 = new MakeUp(1,"白雪公主"); 6 7 m1.start(); 8 m2.start(); 9 } 10 } 11 12 //口紅 13 class Lipstick{ 14 } 15 16 //鏡子 17 class Mirror{ 18 } 19 20 class MakeUp extends Thread{ 21 22 //需要的資源只有一份,用static來保證只有一份 23 static Lipstick lipstick=new Lipstick(); 24 static Mirror mirror=new Mirror(); 25 26 int choice;//選擇 27 String girlName;//使用化妝品的人 28 29 MakeUp(int choice,String girlName){ 30 this.choice=choice; 31 this.girlName=girlName; 32 } 33 34 @Override 35 public void run() { 36 //化妝 37 try { 38 makeup(); 39 } catch (InterruptedException e) { 40 e.printStackTrace(); 41 } 42 } 43 44 //化妝,互相持有對方的鎖,就是需要拿到對方的資源 45 private void makeup() throws InterruptedException { 46 if (choice==0){ 47 synchronized (lipstick){ //獲得口紅的鎖 48 System.out.println(this.girlName+"獲得口紅的鎖"); 49 Thread.sleep(1000); 50 synchronized (mirror){//一秒后獲得鏡子的鎖 51 System.out.println(this.girlName+"獲得鏡子的鎖"); 52 } 53 } 54 }else { 55 synchronized (mirror){//獲得鏡子的鎖 56 System.out.println(this.girlName+"獲得了鏡子的鎖"); 57 Thread.sleep(2000); 58 synchronized (lipstick){//二秒后獲得口紅的鎖 59 System.out.println(this.girlName+"獲得口紅的鎖"); 60 } 61 } 62 } 63 } 64 }1 //死鎖解決,不放在一個synchronized(){}代碼塊中,不讓它報對方的鎖 2 public class DeadLock { 3 public static void main(String[] args) { 4 MakeUp m1 = new MakeUp(0,"灰姑娘"); 5 MakeUp m2 = new MakeUp(1,"白雪公主"); 6 7 m1.start(); 8 m2.start(); 9 } 10 } 11 12 //口紅 13 class Lipstick{ 14 } 15 16 //鏡子 17 class Mirror{ 18 } 19 20 class MakeUp extends Thread{ 21 22 //需要的資源只有一份,用static來保證只有一份 23 static Lipstick lipstick=new Lipstick(); 24 static Mirror mirror=new Mirror(); 25 26 int choice;//選擇 27 String girlName;//使用化妝品的人 28 29 MakeUp(int choice,String girlName){ 30 this.choice=choice; 31 this.girlName=girlName; 32 } 33 34 @Override 35 public void run() { 36 //化妝 37 try { 38 makeup(); 39 } catch (InterruptedException e) { 40 e.printStackTrace(); 41 } 42 } 43 44 //化妝,互相持有對方的鎖,就是需要拿到對方的資源 45 private void makeup() throws InterruptedException { 46 if (choice==0){ 47 synchronized (lipstick){ //獲得口紅的鎖 48 System.out.println(this.girlName+"獲得口紅的鎖"); 49 Thread.sleep(1000); 50 } 51 synchronized (mirror){//一秒后獲得鏡子的鎖 52 System.out.println(this.girlName+"獲得鏡子的鎖"); 53 } 54 }else { 55 synchronized (mirror){//獲得鏡子的鎖 56 System.out.println(this.girlName+"獲得了鏡子的鎖"); 57 Thread.sleep(2000); 58 } 59 synchronized (lipstick){//二秒后獲得口紅的鎖 60 System.out.println(this.girlName+"獲得口紅的鎖"); 61 } 62 } 63 } 64 }
Lock鎖
擁有與synchronized相同的並發性和內存語義,在實現線程安全控制中,比較常用的是ReentrantLock,可以顯式加鎖、釋放鎖
1 //lock鎖 2 public class TestLock { 3 public static void main(String[] args) { 4 TestLock2 testLock2 = new TestLock2(); 5 6 new Thread(testLock2).start(); 7 new Thread(testLock2).start(); 8 new Thread(testLock2).start(); 9 } 10 } 11 12 class TestLock2 implements Runnable { 13 14 int ticketNum=10; 15 16 //定義Lock鎖 17 private final ReentrantLock lock= new ReentrantLock(); 18 19 @Override 20 public void run() { 21 while (true){ 22 try { 23 lock.lock();//加鎖 24 if (ticketNum>0){ 25 try { 26 Thread.sleep(1000); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 System.out.println(ticketNum--); 31 }else { 32 break; 33 } 34 35 }finally { 36 lock.unlock();//解鎖 37 } 38 } 39 } 40 }synchronized 和 Lock 對比
線程通信
應用場景:生產者和消費者問題
- 假設倉庫中只能存放一件產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中產品取走消費
- 如果倉庫中沒有產品,則生產者將產品放入倉庫,否則停止生產並等待,直到倉庫中的產品被消費者取走為止
- 如果倉庫中放有產品,則消費者可以將產品取走消費,否則停止消費並等待,直到倉庫中再次放入產品為止
應用場景:分析
這是一個線程同步問題,生產者和消費者共享同一個資源,並且生產者和消費者之間相互依賴,互為條件
- 對於生產者,沒有生產產品之前,要通知消費者等待。而生產了產品之后,又需要馬上通知消費者消費
- 對於消費者,在消費之后,要通知生產者已經結束消費,需要生產新的產品以供消費。
- 在生產者消費者問題中,僅有synchronized是不夠的
- synchronized可阻止並發更新同一個共享資源,實現了同步
- synchronized不能用來實現不同線程之間的消息傳遞(通信)
- java提供了幾個方法解決線程之間的通信問題
- 注意:均是Object類的方法,都只能在同步方法或者同步代碼塊中使用,否則會拋出異常IIIegaIMonitorStateException
應用場景:解決方式(一) 管程法
並發協作模型“生產者/消費者模式”--->管程法
- 生產者:負責生產數據的模塊(可能是方法,對象,線程,進程)
- 消費者:負責處理數據的模塊(可能是方法,對象,線程,進程)
- 緩沖區:消費者不能直接使用生產者的數據,他們之間有個緩沖區。生產者將生產好的數據放入緩沖區,消費者從緩沖區拿出數據
1 //測試:生產者消費者模型-->利用緩沖區解決:管程法 2 3 //生產者,消費者,產品,緩沖區 4 public class TestPc { 5 public static void main(String[] args) { 6 SynContainer container = new SynContainer(); 7 8 new Productor(container).start(); 9 new Consumer(container).start(); 10 11 } 12 } 13 14 //生產者 15 class Productor extends Thread{ 16 SynContainer container; 17 public Productor(SynContainer container){ 18 this.container=container; 19 } 20 21 //生產 22 @Override 23 public void run() { 24 for (int i = 0; i < 100; i++) { 25 System.out.println("生產了"+i+"只雞"); 26 container.push(new Chicken(i)); 27 } 28 } 29 } 30 31 //消費者 32 class Consumer extends Thread{ 33 SynContainer container; 34 public Consumer(SynContainer container){ 35 this.container=container; 36 } 37 38 //消費 39 40 @Override 41 public void run() { 42 for (int i = 0; i < 100; i++) { 43 System.out.println("消費了--->"+container.pop().id+"只雞"); 44 } 45 } 46 } 47 48 //產品 49 class Chicken{ 50 int id;//產品編號 51 52 public Chicken(int id) { 53 this.id = id; 54 } 55 } 56 57 //緩沖區 58 class SynContainer{ 59 60 //需要一個容器大小 61 Chicken[] chickens=new Chicken[10]; 62 //容器計數器 63 int count=0; 64 65 //生產者放入產品 66 public synchronized void push(Chicken chicken){ 67 //如果容器滿了,就需要等待消費者消費 68 if (count==chickens.length){ 69 //通知消費者消費,生產等待 70 try { 71 this.wait(); 72 } catch (InterruptedException e) { 73 e.printStackTrace(); 74 } 75 } 76 77 //如果沒有滿,我們就需要丟入產品 78 chickens[count]=chicken; 79 count++; 80 81 //可以通知消費者消費 82 this.notifyAll(); 83 } 84 85 //消費者消費產品 86 public synchronized Chicken pop(){ 87 //判斷能否消費 88 if (count==0){ 89 //等待生產者生產,消費者等待 90 try { 91 this.wait(); 92 } catch (InterruptedException e) { 93 e.printStackTrace(); 94 } 95 } 96 97 //如果可以消費 98 count--; 99 Chicken chicken=chickens[count]; 100 101 //吃完了,通知生產者生產 102 this.notifyAll(); 103 return chicken; 104 } 105 }
應用場景:解決方式(二) 信號燈法
並發協作模型“生產者/消費者模式”--->信號燈法
1 //測試生產者消費者問題2:信號燈法,標志位解決 2 public class TestPc2 { 3 public static void main(String[] args) { 4 TV tv = new TV(); 5 new Player(tv).start(); 6 new Wather(tv).start(); 7 8 } 9 10 } 11 //生產者--->演員 12 class Player extends Thread{ 13 TV tv; 14 public Player(TV tv){ 15 this.tv=tv; 16 } 17 18 @Override 19 public void run() { 20 for (int i = 0; i < 20; i++) { 21 if (i%2==0){ 22 this.tv.play("話劇"); 23 }else { 24 this.tv.play("相聲"); 25 } 26 } 27 } 28 } 29 30 //消費者--->觀眾 31 class Wather extends Thread{ 32 TV tv; 33 public Wather(TV tv){ 34 this.tv=tv; 35 } 36 37 @Override 38 public void run() { 39 for (int i = 0; i < 20; i++) { 40 tv.watch(); 41 } 42 } 43 } 44 45 //產品--->節目 46 class TV{ 47 //演員表演,觀眾等待 T 48 //觀眾鼓掌,演員等待 F 49 String voice;//表演的節目 50 boolean flag=true; 51 52 //表演 53 public synchronized void play(String voice){ 54 if (!flag){ 55 try { 56 this.wait(); 57 } catch (InterruptedException e) { 58 e.printStackTrace(); 59 } 60 } 61 System.out.println("演員表演了:"+voice); 62 //通知觀眾鼓掌 63 this.voice=voice; 64 this.notifyAll();//通知喚醒 65 this.flag = !this.flag; 66 } 67 68 //鼓掌 69 public synchronized void watch(){ 70 if (flag){ 71 try { 72 this.wait(); 73 } catch (InterruptedException e) { 74 e.printStackTrace(); 75 } 76 } 77 System.out.println(voice+"觀眾鼓過掌了"); 78 //通知演員繼續表演 79 this.notifyAll(); 80 this.flag = !this.flag; 81 } 82 }
使用線程池
- 背景:經常創建和銷毀、使用量特別大的資源,比如並發情況下的線程,對性能影響很大。
- 思路:提前創建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。可以避免頻繁創建銷毀、實現重復利用。類似生活中的公共交通工具
- 好處:
- 提高響應速度(減少了創建新線程的時間)
- 降低資源消耗(重復利用線程池中的線程,不需要每次創建)
- 便於線程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大線程數
- keepAliveTime:線程沒有任務時最多保持多長時間會終止
Callable在上面創建線程里面已經講述過了,下面是使用Runnable實現線程池操作
1 //測試線程池-Runnable 2 public class TestPool { 3 public static void main(String[] args) { 4 //1.創建服務,創建線程池 5 ExecutorService service = Executors.newFixedThreadPool(10);//參數為線程池大小 6 7 //執行 8 service.execute(new MyThread()); 9 service.execute(new MyThread()); 10 service.execute(new MyThread()); 11 service.execute(new MyThread()); 12 13 //2.關閉連接 14 service.shutdown(); 15 } 16 } 17 18 class MyThread implements Runnable{ 19 @Override 20 public void run() { 21 System.out.println(Thread.currentThread().getName()); 22 } 23 }