日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java多线程详解[狂神说Java]

發(fā)布時間:2025/4/5 java 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java多线程详解[狂神说Java] 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

      • 多線程01: 線程的生命周期
      • 多線程02:創(chuàng)建線程:繼承Thread類
        • 案例:下載圖片
      • 多線程03: 創(chuàng)建線程:實現(xiàn)Runnable接口
        • 案例:多線程操作同一資源對象
        • 案例:龜兔賽跑
      • 多線程04:創(chuàng)建線程:實現(xiàn)Callable接口
      • 多線程05:lambda表達式
      • 多線程06:線程狀態(tài)
      • 多線程07:線程的優(yōu)先級
      • 多線程08:守護線程(daemon)
      • 多線程09:線程同步
      • 多線程10:死鎖
      • 多線程11:線程協(xié)作
      • 參考資料

Java支持多線程編程。線程的概念是什么呢?一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務。

那什么是進程呢? 進程是操作系統(tǒng)分配資源的最小單位,一個進程包括由操作系統(tǒng)分配的內(nèi)存空間、一個或多個線程。注意,線程不能單獨存在,它必須是進程的一部分。一個進程一直運行,直到所有的非守護線程都結束運行后才能結束。注:守護線程(Daemon,發(fā)音:英 [?di?m?n] )

多線程能滿足程序員編寫高效率的程序來達到充分利用cpu的目的。

多線程是多任務的一種特別形式,但多線程使用了更小的資源開銷。

多線程01: 線程的生命周期

線程是一個動態(tài)執(zhí)行的過程,它有一個從產(chǎn)生到死亡的過程。
下圖顯示了一個線程完整的生命周期:

來源:菜鳥教程

線程的五大狀態(tài)

  • 新建狀態(tài)
    使用new關鍵字和Thread類或其子類建立一個線程對象后,該線程對象就處于新建狀態(tài)。它保持這個狀態(tài)直到程序start()這個線程。

  • 就緒狀態(tài)
    當線程對象調用了start()方法之后,該線程就進入了就緒狀態(tài)。就緒狀態(tài)的線程處于就緒隊列中,要等待JVM里線程調度器的調度。

  • 運行狀態(tài)
    如果就緒狀態(tài)的線程獲取CPU的資源,就可以執(zhí)行run()方法,此時線程便處于運行狀態(tài)。處于運行狀態(tài)的線程有多種變化方式,它可以變?yōu)樽枞麪顟B(tài)、就緒狀態(tài)和死亡狀態(tài)。

  • 阻塞狀態(tài)
    如果一個線程執(zhí)行了sleep(睡眠)、suspend(掛起)等方法,失去所占有的資源后,該線程就從運行狀態(tài)轉為阻塞狀態(tài)。在睡眠時間已到或獲得設備資源后可以重新進入就緒狀態(tài)。可以分為三種:
    1 . 等待阻塞:運行狀態(tài)下的線程執(zhí)行wait()方法,使線程進入到等待隊列。
    2 . 同步阻塞:線程在獲得synchronized同步鎖失敗(因為同步鎖被其他線程占用)。
    3 . 其他阻塞:通過調用線程的sleep() 或join()發(fā)出I/O請求時,線程就會進入阻塞狀態(tài)。當sleep()狀態(tài)超時, 或join()等待線程終止或超時,或者I/O處理完畢,線程重新轉入就緒狀態(tài)。

  • 死亡狀態(tài)
    一個運行狀態(tài)的線程 完成任務或其他終止條件發(fā)生時,該線程就切換到終止狀態(tài)。

多線程02:創(chuàng)建線程:繼承Thread類

Java提供了三種創(chuàng)建線程的方法:

  • 通過繼承Thread類本身。
  • 通過實現(xiàn)Runnable接口。
  • 通過Callable和Future創(chuàng)建線程。
  • 輔助幫助文檔:Class Thread


    來源:Java Platform SE 8

    Thread
    三個步驟:1.自定義線程類Thread類;2.重寫run()方法,編寫線程結構體;3.創(chuàng)建線程對象,調用start()方法啟動線程。

    線程不一定立即執(zhí)行,CPU安排調度。

    package lishizheng.demo01;//創(chuàng)建線程方式1:繼承Thread類,重寫run方法,調用start開啟線程 public class TestThread1 extends Thread {@Overridepublic void run() {//run方法線程體for (int i = 0; i < 20; i++) {System.out.println("我在看代碼:" + i);}}public static void main(String[] args) {//main線程,主線程//創(chuàng)建一個線程對象TestThread1 testThread1 = new TestThread1();//調用start方法開啟線程testThread1.start();for (int i = 0; i < 1000; i++) {System.out.println("我在學習多線程: " + i);}} }

    案例:下載圖片

    首先下載名為commons-io的jar包:Download Apache Commons IO

    解壓之后如下圖:

    復制上圖中的jar包,在IDEA項目中新建文件夾lib,然后選中它,然后Ctrl+V,將jar包導入項目。

    導入后,右擊lib文件夾,選擇Add As Library

    彈出如下界面:點擊OK即可。

    代碼如下:

    package lishizheng.demo01;import org.apache.commons.io.FileUtils;import java.io.File; import java.io.IOException; import java.net.URL;//練習Thread,實現(xiàn)多線程下載圖片 public class TestThread2 extends Thread {private String url; //網(wǎng)絡圖片地址private String name; //保存的文件名public TestThread2(String url, String name){this.url = url;this.name = name;}//下載圖片線程的執(zhí)行體@Overridepublic void run() {WebDownLoader webDownLoader = new WebDownLoader();webDownLoader.downloader(url,name);System.out.println("已下載文件名為:" + name + "的圖片");}public static void main(String[] args) {//確保圖片ulr是正確的TestThread2 t1 = new TestThread2("https://img-blog.csdnimg.cn/20210210194321356.png","我的博客圖片01.png");TestThread2 t2 = new TestThread2("https://img-blog.csdnimg.cn/20210228094648502.png","我的博客圖片02.png");TestThread2 t3 = new TestThread2("https://img-blog.csdnimg.cn/20210303104522231.png","我的博客圖片03.png");t1.start(); //啟動線程t2.start();t3.start();} }//下載器 class WebDownLoader{//下載方法public void downloader(String url, String name){try {FileUtils.copyURLToFile(new URL(url), new File(name)); //調用FileUtils類中的copyURLToFile方法實現(xiàn)下載} catch (IOException e) {e.printStackTrace();System.out.println("IO異常:downloader方法出現(xiàn)問題");}} } /* 執(zhí)行結果: 已下載文件名為:我的博客圖片01.png的圖片 已下載文件名為:我的博客圖片02.png的圖片 已下載文件名為:我的博客圖片03.png的圖片Process finished with exit code 0*/

    多線程03: 創(chuàng)建線程:實現(xiàn)Runnable接口

    實現(xiàn)Runnable步驟:1.定義MyRunnable類實現(xiàn)Runnable接口;2.實現(xiàn)run()方法,編寫線程執(zhí)行體;3.創(chuàng)建線程對象,調用start()方法啟動線程。

    創(chuàng)建一個線程,最簡單的方法是創(chuàng)建一個實現(xiàn) Runnable 接口的類。如下:

    public class TestThread3 implements Runnable{}

    在創(chuàng)建一個實現(xiàn) Runnable 接口的類之后可以在類中實例化一個線程對象:

    //創(chuàng)建Runnable接口的實現(xiàn)類對象TestThread3 testThread3 = new TestThread3();//創(chuàng)建線程對象,通過線程對象來開啟我們的線程,代理Thread thread = new Thread(testThread3);

    新線程創(chuàng)建之后,許喲啊調用它的 start() 方法它才會運行:

    thread.start();

    創(chuàng)建對象并調用start方法可以用一句代碼實現(xiàn):

    new Thread(testThread3).start();//等價寫法

    全部代碼舉例如下:

    package lishizheng.demo01;//創(chuàng)建線程方式2:實現(xiàn)Runnable接口,重寫run方法,執(zhí)行線程需要丟入實現(xiàn)Runnable接口的實現(xiàn)類 public class TestThread3 implements Runnable {@Overridepublic void run() {//run方法線程體for (int i = 0; i < 20; i++) {System.out.println("我在看代碼:" + i);}}public static void main(String[] args) {//創(chuàng)建Runnable接口的實現(xiàn)類對象TestThread3 testThread3 = new TestThread3();//創(chuàng)建線程對象,通過線程對象來開啟我們的線程,代理Thread thread = new Thread(testThread3);thread.start();//等價寫法:// new Thread(testThread3).start();for (int i = 0; i < 1000; i++) {System.out.println("我在學習多線程: " + i);}} }/* 運行代碼其中一部分:可以發(fā)現(xiàn)線程的執(zhí)行順序是由CPU來調度的!!!并不完全按照代碼書寫的順序。 我在學習多線程: 0 我在看代碼:0 我在學習多線程: 1 我在學習多線程: 2 我在學習多線程: 3 我在學習多線程: 4 我在學習多線程: 5 我在學習多線程: 6 我在學習多線程: 7 我在學習多線程: 8 我在學習多線程: 9 我在學習多線程: 10 我在學習多線程: 11 我在學習多線程: 12 我在學習多線程: 13 我在學習多線程: 14 我在學習多線程: 15 我在看代碼:1 我在學習多線程: 16 我在看代碼:2 我在學習多線程: 17 我在看代碼:3 我在學習多線程: 18*/

    小結:

    • 繼承Thread類
  • 子類繼承Thread類具有多線程能力
  • 啟動線程:子類對象.start()
  • 不建議使用:避免OOP單繼承局限性
    • 實現(xiàn)Runnable接口
  • 實現(xiàn)接口Runnable具有多線程能力
  • 啟動線程: 傳入目標對象+Thread對象.start()
  • 推薦使用: 避免單繼承局限性,靈活方便,方便同一對象被多個線程使用
  • 案例:多線程操作同一資源對象

    下面的代碼實現(xiàn)搶票的功能:共有10張火車票,三個人來搶。目的是學習并體會多線程對同一個資源對象操作的情況。

    補充Thread.currentThread().getName()獲取線程的名字

    package lishizheng.demo01;//多個線程同時操作同一個對象 //買火車票的例子//發(fā)現(xiàn)問題:多個線程操作同一個資源的情況下,線程不安全,數(shù)據(jù)紊亂 public class TestThread4 implements Runnable {private int ticketNums = 10;@Override//重寫run方法public void run() {while (true){if(ticketNums <= 0) break;System.out.println( Thread.currentThread().getName() + "拿到了第 " + ticketNums-- +"張票");}}public static void main(String[] args) {TestThread4 testThread4 = new TestThread4();// 一個對象,三個線程 new Thread(testThread4, "小明").start();new Thread(testThread4,"老師").start();new Thread(testThread4,"黃牛").start();} } /* 輸出結果如下: 黃牛拿到了第 10張票 老師拿到了第 8張票 小明拿到了第 9張票 老師拿到了第 6張票 黃牛拿到了第 7張票 老師拿到了第 4張票 小明拿到了第 5張票 老師拿到了第 2張票 黃牛拿到了第 3張票 小明拿到了第 1張票Process finished with exit code 0*/

    案例:龜兔賽跑

    模擬龜兔賽跑,兩者在同一條跑道,先跑到100步者為勝利者。
    用意:體會多線程競爭資源。

    下面使用到下圖中的兩個方法:


    來源:菜鳥教程

    package lishizheng.demo01;public class Race implements Runnable{private static String winner;@Overridepublic void run() {for (int i = 0; i <= 100; i++) {//模擬兔子休息if(Thread.currentThread().getName().equals("兔子") && i % 30 == 0){try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}//判斷比賽是否結束boolean result = gameOver(i);if(result) break;System.out.println(Thread.currentThread().getName() + " 跑了 " + i + "步");}}//判斷是否完成比賽private boolean gameOver(int steps){//已經(jīng)存在勝利者if(winner != null){return true;}if( steps >= 100){winner = Thread.currentThread().getName();System.out.println("Winner is : " + winner);return true;}return false;}public static void main(String[] args) {//new 一個賽道Race race = new Race();//兩個線程同時競爭同一個賽道new Thread(race ,"兔子").start();new Thread(race ,"烏龜").start();} } /* 輸出結果,其中一部分: 兔子 跑了 22步 烏龜 跑了 98步 兔子 跑了 23步 兔子 跑了 24步 烏龜 跑了 99步 兔子 跑了 25步 Winner is : 烏龜Process finished with exit code 0*/

    多線程04:創(chuàng)建線程:實現(xiàn)Callable接口

    實現(xiàn)Callable接口的步驟:

  • 實現(xiàn)Callable接口,需要返回值類型
  • 重寫call()方法,需要拋出異常
  • 創(chuàng)建目標對象
  • 創(chuàng)建執(zhí)行服務:ExecutorService ser = Executor.newFixedThreadPool(1);
  • 提交執(zhí)行:Future<Boolean> result1 = ser.submit(t1);
  • 獲取結果:boolean r1 = result1.get();
  • 關閉服務:ser.shutdownNow();
  • 利用Callable修改上面下載圖片案例

    package lishizheng.demo02;import org.apache.commons.io.FileUtils;import java.io.File; import java.io.IOException; import java.net.URL; import java.util.concurrent.*;//線程創(chuàng)建3:實現(xiàn)Callable接口 /* * * */ public class TestCallable implements Callable<Boolean> {private String url; //網(wǎng)絡圖片地址private String name; //保存的文件名public TestCallable(String url, String name){this.url = url;this.name = name;}//下載圖片線程的執(zhí)行體@Overridepublic Boolean call() {WebDownLoader webDownLoader = new WebDownLoader();webDownLoader.downloader(url,name);System.out.println("已下載文件名為:" + name + "的圖片");return true;}public static void main(String[] args) throws ExecutionException, InterruptedException {//確保圖片ulr是正確的TestCallable t2 = new TestCallable("https://img-blog.csdnimg.cn/20210228094648502.png","我的博客圖片02.png");TestCallable t3 = new TestCallable("https://img-blog.csdnimg.cn/20210303104522231.png","我的博客圖片03.png");TestCallable t1 = new TestCallable("https://img-blog.csdnimg.cn/20210210194321356.png","我的博客圖片01.png");//創(chuàng)建執(zhí)行服務ExecutorService ser = Executors.newFixedThreadPool(3);//提交執(zhí)行Future<Boolean> r1 = ser.submit(t1);Future<Boolean> r2 = ser.submit(t2);Future<Boolean> r3 = ser.submit(t3);//獲取結果boolean res1 = r1.get();boolean res2 = r2.get();boolean res3 = r3.get();//關閉服務ser.shutdown();} }//下載器 class WebDownLoader{//下載方法public void downloader(String url, String name){try {FileUtils.copyURLToFile(new URL(url), new File(name)); //調用FileUtils類中的copyURLToFile方法實現(xiàn)下載} catch (IOException e) {e.printStackTrace();System.out.println("IO異常:downloader方法出現(xiàn)問題");}} }/* 已下載文件名為:我的博客圖片01.png的圖片 已下載文件名為:我的博客圖片03.png的圖片 已下載文件名為:我的博客圖片02.png的圖片Process finished with exit code 0 */

    多線程05:lambda表達式

    Lambda 表達式,也可稱為閉包,它是推動 Java 8 發(fā)布的最重要新特性。

    Lambda 允許把函數(shù)作為一個方法的參數(shù)(函數(shù)作為參數(shù)傳遞進方法中)。

    使用 Lambda 表達式可以使代碼變的更加簡潔緊湊。

    語法:

    (parameters) -> expression 或 (parameters) -> {statements;}

    lambda表達式的重要特征:

  • 可選類型聲明
    不需要聲明參數(shù)類型,編譯器可以統(tǒng)一識別參數(shù)值。

  • 可選的參數(shù)圓括號
    一個參數(shù)無需定義圓括號,但多個參數(shù)需要定義圓括號

  • 可選的大括號
    如果主體包含了一個語句,就不需要使用大括號

  • 可選的返回關鍵字
    如果主體只有一個表達式返回值則編譯器會自動返回值,大括號需要指明表達式返回了一個數(shù)值。

  • 函數(shù)式接口(Functional Interface)就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口

    對于函數(shù)式接口我們可以使用lambda表達式來創(chuàng)建該接口的對象。

    //1.定義一個函數(shù)式接口 interface ILike{void lambda(); }//6.用lambda簡化ILike like5 = ()-> {System.out.println("I like Lambda5");};like5.lambda();/* 輸出:I like Lambda5 */

    下面是推導lambda表達式的過程,一步一步化簡,思路是先從用類實現(xiàn)接口,到靜態(tài)內(nèi)部類,到局部內(nèi)部類,再到匿名內(nèi)部類,一步步簡化,到最后是lambda表達式。

    package lishizheng.demo03;//推導lambda表達式 public class TestLambda {//3.靜態(tài)內(nèi)部類static class Like2 implements ILike{@Overridepublic void lambda() {System.out.println("I like Lambda2");}}public static void main(String[] args) {Like like = new Like();like.lambda();Like2 like2 = new Like2();like2.lambda();//4. 局部內(nèi)部類class Like3 implements ILike{@Overridepublic void lambda() {System.out.println("I like Lambda3");}}Like3 like3 = new Like3();like3.lambda();//5.匿名內(nèi)部類,沒有類的名稱,必須借助接口或者父類ILike like4 = new ILike() {@Overridepublic void lambda() {System.out.println("I like Lambda4");}};like4.lambda();//6.用lambda簡化ILike like5 = ()-> {System.out.println("I like Lambda5");};like5.lambda();} }//1.定義一個函數(shù)式接口 interface ILike{void lambda(); }//2.實現(xiàn)類 class Like implements ILike{@Overridepublic void lambda() {System.out.println("I like Lambda");} }/* 輸出結果: I like Lambda I like Lambda2 I like Lambda3 I like Lambda4 I like Lambda5Process finished with exit code 0 */

    靜態(tài)代理模式

    //真實對象和代理對象到實現(xiàn)同一個接口
    //代理對象要代理真實對象

    好處:代理對象可以做很多真實對象做不了的事情 ;真實對象專注于自己的事情

    舉例:下面通過婚慶公司代理你來組織婚禮來說明一下靜態(tài)代理模式的功能。結婚之前要布置現(xiàn)場,然后主人結婚,結婚之后收尾款,這些方法都由婚慶公司對象來調用。

    package lishizheng.demo04;//靜態(tài)代理模式//真實對象和代理對象到實現(xiàn)同一個接口 //代理對象到代理真實對象//好處:代理對象可以做很多真實對象做不了的事情 ;真實對象專注于自己的事情public class StaticProxy {public static void main(String[] args) {You you = new You();WeddingCompany weddingCompany = new WeddingCompany(you);weddingCompany.happyMarry();} }interface Marry{void happyMarry(); }class You implements Marry{@Overridepublic void happyMarry() {System.out.println("結婚,超開心");} }//代理角色 class WeddingCompany implements Marry{//代理誰? 真實目標角色private Marry target;public WeddingCompany(Marry target) {this.target = target;}@Overridepublic void happyMarry() {before();this.target.happyMarry();//真實對象after();}private void after() {System.out.println("結婚之后收尾款");}private void before() {System.out.println("結婚之前布置現(xiàn)場");} } /* 輸出結果:結婚之前布置現(xiàn)場 結婚,超開心 結婚之后收尾款Process finished with exit code 0 */

    和多線程有什么關系呢?

    復習Thread調用方法的時候,原理是一樣的,它本身是Runnable接口的代理。 下面通過婚慶公司和線程進行對比。

    public class StaticProxy {public static void main(String[] args) {You you = new You();//Thread代理真實的Runnable接口,new Thread(() -> System.out.println("我愛你") ).start();new WeddingCompany(you).happyMarry();} }

    多線程06:線程狀態(tài)

    停止線程stop

    • 不推薦使用JDK提供的stop方法,destroy方法
    • 推薦讓線程自己停止下來,建議使用標志位進行終止變量:當flag == false時,線程終止。

    舉例如下: 測試主線程和teststop線程的執(zhí)行過程,運行期間讓teststop停止。

    package lishizheng.demo05;//測試stop //1.建議線程正常停止:利用此時,不建議死循環(huán) //2.建議使用標志位 public class TestStop implements Runnable {// 1.設置一個標志位private boolean flag = true;@Overridepublic void run() {int i = 0;while (flag){System.out.println("run...Thread: " + i++);}}//2.設置公開的方法停止線程public void stop(){this.flag = false;}public static void main(String[] args) {TestStop testStop = new TestStop();new Thread(testStop).start();for (int i = 0; i < 30; i++) {System.out.println("main " +i);if( i == 20){//調用stop方法切換線程標志位,讓線程停止testStop.stop();System.out.println("線程已停止");}}} }/* 輸出結果: main 0 run...Thread: 0 main 1 run...Thread: 1 main 2 run...Thread: 2 main 3 run...Thread: 3 main 4 main 5 run...Thread: 4 main 6 run...Thread: 5 main 7 main 8 main 9 main 10 main 11 main 12 main 13 main 14 main 15 main 16 main 17 main 18 main 19 main 20 run...Thread: 6 線程已停止 main 21 main 22 main 23 main 24 main 25 main 26 main 27 main 28 main 29Process finished with exit code 0*/

    線程休眠sleep

    • sleep指定當前線程阻塞的毫秒數(shù),時間到后線程進入就緒狀態(tài)
    • sleep存在異常InterruptedException,要拋出
    • sleep可以模擬網(wǎng)絡延遲,倒計時等
    • 每個對象都有一個鎖,sleep不會釋放鎖。

    舉例:sleep模擬網(wǎng)絡延時,發(fā)現(xiàn)代碼的漏洞:這里是多線程操作同一個對象,造成線程不安全。通過sleep延時可以發(fā)現(xiàn)程序的執(zhí)行過程并不是我們預計的那樣。

    package lishizheng.demo05;//模擬網(wǎng)絡延時:放大問題的可能性,容易發(fā)現(xiàn)問題 public class TestSleep implements Runnable{private int ticketNums = 10;@Overridepublic void run() {while (true){if(ticketNums <= 0) break;try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println( Thread.currentThread().getName() + "拿到了第 " + ticketNums-- +"張票");}}public static void main(String[] args) {//線程不安全:多個線程操作同一個對象TestSleep testThread4 = new TestSleep();new Thread(testThread4, "小明").start();new Thread(testThread4,"老師").start();new Thread(testThread4,"黃牛").start();} }/* 運行結果: 黃牛拿到了第 9張票 老師拿到了第 8張票 小明拿到了第 10張票 老師拿到了第 7張票 小明拿到了第 7張票 黃牛拿到了第 6張票 黃牛拿到了第 5張票 小明拿到了第 4張票 老師拿到了第 5張票 小明拿到了第 3張票 黃牛拿到了第 1張票 老師拿到了第 2張票Process finished with exit code 0 */

    舉例
    模擬倒計時和模擬獲取系統(tǒng)時間

    package lishizheng.demo05;import java.text.SimpleDateFormat; import java.util.Date; import java.util.SimpleTimeZone;//模擬倒計時 public class TestSleep2 {public static void main(String[] args) { // try { // timeDown(); // } catch (InterruptedException e) { // // }//打印當前系統(tǒng)時間Date startTime = new Date(System.currentTimeMillis());//獲取系統(tǒng)當前時間while (true){try {Thread.sleep(1000);System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));startTime = new Date(System.currentTimeMillis());//更新時間} catch (InterruptedException e) {e.printStackTrace();}}}//模擬倒計時:10~1public static void timeDown() throws InterruptedException {int num = 10;while (true){Thread.sleep(1000);System.out.println(num--);if(num < 0) break;}} }/* 輸出: 12:02:16 12:02:17 12:02:18 12:02:19 12:02:20 12:02:21 12:02:22*/

    線程禮讓yield

    線程禮讓,讓當前正在執(zhí)行的線程暫停,但不阻塞;將線程從運行狀態(tài)轉為就緒狀態(tài)。讓CPU重新調度,禮讓不一定成功,看CPU心情。

    測試:

    package lishizheng.demo05;//測試禮讓線程 //禮讓不一定成功,看CPU心情 public class TestYield {public static void main(String[] args) {MyYield myYield = new MyYield();new Thread(myYield,"a").start();new Thread(myYield,"b").start();} }class MyYield implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"線程開始執(zhí)行");Thread.yield();System.out.println(Thread.currentThread().getName()+"線程停止執(zhí)行");} } /*a線程開始執(zhí)行 b線程開始執(zhí)行 a線程停止執(zhí)行 b線程停止執(zhí)行Process finished with exit code 0 */

    join
    join相當于線程插隊,執(zhí)行完插隊線程才能繼續(xù)執(zhí)行別的線程。
    測試:

    package lishizheng.demo05;//測試join方法 //想象為插隊public class TestJoin implements Runnable{@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("線程vip來了:" +i);}}public static void main(String[] args) throws InterruptedException {//啟動我們的線程TestJoin testJoin = new TestJoin();Thread thread = new Thread(testJoin);thread.start();//主線程for (int i = 0; i < 500; i++) {if( i == 100){thread.join(); //插隊,主線程等待插隊線程運行結束}System.out.println("main "+i);}} }


    觀察線程狀態(tài):

    package lishizheng.demo05;//觀察測試線程的狀態(tài) public class TestState {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() ->{for (int i = 0; i < 5; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("=============");});//觀察狀態(tài)Thread.State state = thread.getState();System.out.println("狀態(tài):"+ state);//觀察啟動后thread.start();state = thread.getState();System.out.println("啟動后狀態(tài):"+state);while (state != Thread.State.TERMINATED){Thread.sleep(100);state = thread.getState();//更新線程狀態(tài)System.out.println("現(xiàn)在的狀態(tài)是:"+state);}} } /* 狀態(tài):NEW 啟動后狀態(tài):RUNNABLE 現(xiàn)在的狀態(tài)是:TIMED_WAITING 現(xiàn)在的狀態(tài)是:TIMED_WAITING 現(xiàn)在的狀態(tài)是:TIMED_WAITING 現(xiàn)在的狀態(tài)是:TIMED_WAITING 現(xiàn)在的狀態(tài)是:TIMED_WAITING 現(xiàn)在的狀態(tài)是:TIMED_WAITING 現(xiàn)在的狀態(tài)是:TIMED_WAITING 現(xiàn)在的狀態(tài)是:TIMED_WAITING 現(xiàn)在的狀態(tài)是:TIMED_WAITING 現(xiàn)在的狀態(tài)是:TIMED_WAITING ============= 現(xiàn)在的狀態(tài)是:TERMINATEDProcess finished with exit code 0*/

    多線程07:線程的優(yōu)先級

    每一個 Java 線程都有一個優(yōu)先級,這樣有助于操作系統(tǒng)確定線程的調度順序。

    Java 線程的優(yōu)先級是一個整數(shù),其取值范圍是 1 ~10(Thread.MIN_PRIORITY ~Thread.MAX_PRIORITY )。

    默認情況下,每一個線程都會分配一個優(yōu)先級 NORM_PRIORITY(5)。

    具有較高優(yōu)先級的線程對程序更重要,并且應該在低優(yōu)先級的線程之前分配處理器資源。但是,線程優(yōu)先級不能保證線程執(zhí)行的順序,而且非常依賴于平臺。 優(yōu)先級高的線程,先執(zhí)行的概率大。

    測試線程優(yōu)先級

    package lishizheng.demo05;public class TestPriority {public static void main(String[] args) {//主線程默認優(yōu)先級System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());MyPriority myPriority = new MyPriority();Thread t1 = new Thread(myPriority);Thread t2 = new Thread(myPriority);Thread t3 = new Thread(myPriority);Thread t4 = new Thread(myPriority);Thread t5 = new Thread(myPriority);//先設置優(yōu)先級t1.start();t2.setPriority(1);t2.start();t3.setPriority(4);t3.start();t4.setPriority(Thread.MAX_PRIORITY);t4.start();} }class MyPriority implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());} }/* main--->5 Thread-0--->5 Thread-2--->4 Thread-3--->10 Thread-1--->1Process finished with exit code 0 */

    多線程08:守護線程(daemon)

    守護線程是指為其他線程服務的線程。在JVM中,所有非守護線程都執(zhí)行完畢后,無論有沒有守護線程,虛擬機都會自動退出。

    線程分為用戶線程和守護線程,虛擬機必須確保用戶線程執(zhí)行完畢,虛擬機不用等待守護線程執(zhí)行完畢,守護線程比如監(jiān)控內(nèi)存、垃圾回收等。

    測試用例:守護線程thread.setDaemon(true);//默認是false,表示是用戶線程會在JVM結束后接著運行一段時間。

    package lishizheng.demo05;//測試守護線程 public class TestDaemon {public static void main(String[] args) {God god = new God();You you = new You();Thread thread = new Thread(god);thread.setDaemon(true);//默認是false,表示是用戶線程thread.start();//上帝線程new Thread(you).start();//用戶線程啟動} }class God implements Runnable{@Overridepublic void run() {while (true){System.out.println("上帝保佑著你");}} }class You implements Runnable{@Overridepublic void run() {for (int i = 1; i < 100; i++) {System.out.println("已經(jīng)開心活過了"+ i +"年");}System.out.println("goodbye world");} }/* 部分運行結果 已經(jīng)開心活過了92年 已經(jīng)開心活過了93年 已經(jīng)開心活過了94年 已經(jīng)開心活過了95年 已經(jīng)開心活過了96年 已經(jīng)開心活過了97年 已經(jīng)開心活過了98年 已經(jīng)開心活過了99年 goodbye world 上帝保佑著你 上帝保佑著你 上帝保佑著你 上帝保佑著你 上帝保佑著你 Process finished with exit code 0*/

    多線程09:線程同步

    線程同步是控制線程執(zhí)行的先后順序。

    線程同步:即當有一個線程在對內(nèi)存進行操作時,其他線程都不可以對這個內(nèi)存地址進行操作,直到該線程完成操作, 其他線程才能對該內(nèi)存地址進行操作,而其他線程又處于等待狀態(tài),實現(xiàn)線程同步的方法有很多,臨界區(qū)對象就是其中一種。

    多線程同時讀寫共享變量時,會造成邏輯錯誤,因此需要通過synchronized同步;

    線程不安全舉例:

    package lishizheng.demo06;//不安全地買票 public class UnsafeBuyTicket {public static void main(String[] args) {BuyTicket buyTicket = new BuyTicket();new Thread(buyTicket,"小米").start();new Thread(buyTicket,"小明").start();new Thread(buyTicket,"黃牛").start();} }class BuyTicket implements Runnable{private int ticketNums = 10;boolean flag = true;@Overridepublic void run() {//模擬延時try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//買票while (flag){buy();}}private void buy(){if(ticketNums <= 0) {flag = false;return;}System.out.println(Thread.currentThread().getName() + " 拿到" + ticketNums--);} }

    同步方法

    同步塊:synchronized (Obj) {},Obj稱為同步監(jiān)視器。

    • Obj可以是任何對象,但是推薦使用共享資源作為同步監(jiān)視器
    • 同步方法中無需指定同步監(jiān)視器,因為同步方法的同步監(jiān)視器就是this,就是這個對象本身或者是class

    同步監(jiān)視器的執(zhí)行過程

  • 第一個線程訪問,鎖定同步監(jiān)視器,執(zhí)行其中代碼
  • 第二個線程訪問,發(fā)現(xiàn)同步監(jiān)視器被鎖定,無法訪問。
  • 第一個線程訪問完畢,解鎖同步監(jiān)視器。
  • 第二個線程訪問,發(fā)現(xiàn)同步監(jiān)視器沒有鎖,然后鎖定并訪問。
  • 擴充指示JUC:并發(fā)編程的安全性。

    package lishizheng.demo06;import java.util.concurrent.CopyOnWriteArrayList;//測試JUC安全類型的集合 public class TestJUC {public static void main(String[] args) throws InterruptedException {CopyOnWriteArrayList<String > list = new CopyOnWriteArrayList<String >();for (int i = 0; i < 10000; i++) {new Thread(() -> {list.add(Thread.currentThread().getName());}).start();}Thread.sleep(3000);System.out.println(list.size());} }

    多線程10:死鎖

    多個線程各自占有一些共享資源,并且互相等待其他線程占有的資源才能運行,而導致兩個或多個線程都在等待對方釋放資源,都停止運行的情形。某個同步塊擁有"兩個以上對象的鎖"時,就可能發(fā)生死鎖。

    死鎖舉例 :
    灰姑娘和白雪公主都喜歡化妝,這里的化妝需要鏡子和口紅兩者都具備才能完成。 當灰姑娘拿到口紅,而白雪拿到鏡子的時候,兩者相互等待,這樣就構成死鎖。

    程序進入死鎖(卡死),如下圖:

    測試代碼:

    package lishizheng.demo06;//死鎖:多個線程互相持有對方所需的資源,然后形成僵持。 public class DeadLock {public static void main(String[] args) {Makeup girl1 = new Makeup(0,"灰姑娘");Makeup girl2 = new Makeup(1,"白雪公主");//啟動線程girl1.start();girl2.start();} }//口紅 class Lipstick{ }//鏡子 class Mirror{ }class Makeup extends Thread{//需要的資源只有一份,用static來保證只有一份static Lipstick lipstick = new Lipstick();static Mirror mirror = new Mirror();int choice; // 選擇String girlName; //使用化妝品的人Makeup(int myChoice, String myGirlName){choice = myChoice;girlName = myGirlName;}@Overridepublic void run() {//化妝try {makeup();} catch (InterruptedException e) {e.printStackTrace();}}//化妝,互相持有對方的鎖,即需要拿到對方的資源private void makeup() throws InterruptedException {if(choice == 0){synchronized (lipstick){//獲得口紅的鎖System.out.println(girlName + "獲得口紅的鎖");Thread.sleep(1000);synchronized (mirror){System.out.println(girlName + "獲得鏡子的鎖");}}}else{synchronized (mirror){System.out.println(girlName + "獲得鏡子的鎖");Thread.sleep(2000);synchronized (lipstick){System.out.println(girlName + "獲得口紅的鎖");}}}} }

    死鎖避免的方法

    產(chǎn)生死鎖的四個必要條件:

  • 互斥條件:一個資源一次只能被一個進程使用
  • 請求和保護條件:一個進程因請求資源被阻塞時,對已獲得的資源保持不放
  • 不剝奪條件:進程已獲得的資源,在未使用之前不能強制剝奪。
  • 循環(huán)等待條件:若干進程之間形成一種頭尾相連的循環(huán)等待關系
  • 只要想辦法破壞上面任意一個或者多個就可以避免死鎖。

    Lock(鎖)
    從JDK 5.0開始,Java提供了更強大的線程同步機制:通過顯式定義同步鎖對象來實現(xiàn)同步。同步鎖使用Lock對象充當。java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行訪問的工具。

    鎖提供了對共享資源的獨占訪問,每次只能有1個對象對Lock對象加鎖,線程開始訪問共享資源之前要先獲得Lock對象。

    ReentrantLock類實現(xiàn)了Lock,它擁有了與synchronized相同的并發(fā)性和內(nèi)存語義,在實現(xiàn)線程安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖、釋放鎖。

    測試代碼:使用reentrantLock顯式定義鎖和解鎖。

    package lishizheng.advance;import java.util.concurrent.locks.ReentrantLock;//測試Lock鎖 public class TestLock {public static void main(String[] args) {TestLock2 testLock2 = new TestLock2();new Thread(testLock2).start();new Thread(testLock2).start();new Thread(testLock2).start();} }class TestLock2 implements Runnable{private int ticketNums = 10;//定義lock鎖//可重入鎖 re + entrant + lock private final ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true){try {lock.lock();//加鎖if(ticketNums > 0){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(ticketNums--);}else break;}finally {//解鎖lock.unlock();}}} }

    synchronized 和Lock對比

  • Lock是顯式鎖,需要手動上鎖和解鎖;synchronized是隱式鎖,出了作用域自動釋放。
  • Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖。
  • 使用Lock鎖,JVM將花費較小時間來調度線程,性能更好。并且具有更好的擴展性。
  • 優(yōu)先使用順序:Lock 》同步代碼塊 》同步方法
  • 多線程11:線程協(xié)作

    生產(chǎn)者消費者問題

    生產(chǎn)者消費者問題(英語:Producer-consumer problem),也稱有限緩沖問題(Bounded-buffer problem),是一個多進程同步問題的經(jīng)典案例。該問題描述了共享固定大小緩沖區(qū)的兩個進程——即所謂的“生產(chǎn)者”和“消費者”——在實際運行時會發(fā)生的問題。生產(chǎn)者的主要作用是生成一定量的數(shù)據(jù)放到緩沖區(qū)中,然后重復此過程。與此同時,消費者也在緩沖區(qū)消耗這些數(shù)據(jù)。該問題的關鍵就是要保證生產(chǎn)者不會在緩沖區(qū)滿時加入數(shù)據(jù),消費者也不會在緩沖區(qū)中空時消耗數(shù)據(jù)。
    來源:維基百科

    線程通信
    Java提供了幾個方法解決線程之間的通信問題

    方法名作用
    wait()表示線程一直等待,直到其他線程的通知,會釋放鎖
    wait(long tiimeout)指定等待的毫秒數(shù)
    notify()喚醒一個處于等待狀態(tài)的線程
    notifyAll()喚醒同一個對象上所有調用wait()方法的線程,優(yōu)先級別高的線程優(yōu)先被調度

    注意:均為Object類的方法,都只能在同步方法或者同步代碼塊中使用,否則會拋出異常IllegalMonitorStateException.

    解決方法1:管程法

    生產(chǎn)者將生產(chǎn)好的數(shù)據(jù)放入緩沖區(qū),消費者從緩沖區(qū)拿數(shù)據(jù)。

    package lishizheng.demo06;//測試生產(chǎn)者消費者,利用緩沖區(qū)解決,即管程法//what do we need? //生產(chǎn)者,消費者,產(chǎn)品,緩沖區(qū) public class TestPC {public static void main(String[] args) {SynContainer container = new SynContainer();new Producer(container).start();new Consumer(container).start();} }//生產(chǎn)者 class Producer extends Thread{SynContainer container;public Producer( SynContainer container){this.container = container;}//生產(chǎn)@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("生產(chǎn)了 " + i + "只雞");container.push(new Chicken(i));}} }//消費者class Consumer extends Thread{SynContainer container;public Consumer( SynContainer container){this.container = container;}//消費@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("消費了第 " + container.pop().id + " 只雞");}} }//產(chǎn)品class Chicken{int id;public Chicken(int id) {this.id = id;} }//緩沖區(qū) class SynContainer{//容器大小10Chicken[] chickens = new Chicken[10];//容器計數(shù)器int count = 0;//生產(chǎn)者放入產(chǎn)品public synchronized void push(Chicken myChicken){//如果容器滿了,等待消費者消費if(count == 10){//通知消費者消費,生產(chǎn)者等待try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//如果未滿,則放入產(chǎn)品chickens[count] = myChicken;count++;//進程通信,通知 消費者消費this.notifyAll();}//消費者消費產(chǎn)品public synchronized Chicken pop(){//判斷能否消費if(count == 0){//等待生產(chǎn)者生產(chǎn),消費者等待try {wait();} catch (InterruptedException e) {e.printStackTrace();}}//如果可以消費count --;Chicken chicken = chickens[count];//吃完了,通知生產(chǎn)者生產(chǎn)this.notifyAll();return chicken;} }

    解決方法2:信號燈法
    暫無。

    參考資料

    [1]https://www.bilibili.com/video/BV1V4411p7EF?p=1
    [2]https://www.runoob.com/java/java-multithreading.html

    感謝您閱讀到最后,祝您一切順利。

    總結

    以上是生活随笔為你收集整理的Java多线程详解[狂神说Java]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。