概述
- 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 }