Java多线程详解[狂神说Java]
文章目錄
- 多線程01: 線程的生命周期
- 多線程02:創(chuàng)建線程:繼承Thread類
- 案例:下載圖片
- 多線程03: 創(chuàng)建線程:實(shí)現(xiàn)Runnable接口
- 案例:多線程操作同一資源對象
- 案例:龜兔賽跑
- 多線程04:創(chuàng)建線程:實(shí)現(xiàn)Callable接口
- 多線程05:lambda表達(dá)式
- 多線程06:線程狀態(tài)
- 多線程07:線程的優(yōu)先級
- 多線程08:守護(hù)線程(daemon)
- 多線程09:線程同步
- 多線程10:死鎖
- 多線程11:線程協(xié)作
- 參考資料
Java支持多線程編程。線程的概念是什么呢?一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。
那什么是進(jìn)程呢? 進(jìn)程是操作系統(tǒng)分配資源的最小單位,一個(gè)進(jìn)程包括由操作系統(tǒng)分配的內(nèi)存空間、一個(gè)或多個(gè)線程。注意,線程不能單獨(dú)存在,它必須是進(jìn)程的一部分。一個(gè)進(jìn)程一直運(yùn)行,直到所有的非守護(hù)線程都結(jié)束運(yùn)行后才能結(jié)束。注:守護(hù)線程(Daemon,發(fā)音:英 [?di?m?n] )
多線程能滿足程序員編寫高效率的程序來達(dá)到充分利用cpu的目的。
多線程是多任務(wù)的一種特別形式,但多線程使用了更小的資源開銷。
多線程01: 線程的生命周期
線程是一個(gè)動態(tài)執(zhí)行的過程,它有一個(gè)從產(chǎn)生到死亡的過程。
下圖顯示了一個(gè)線程完整的生命周期:
來源:菜鳥教程
線程的五大狀態(tài)
-
新建狀態(tài)
使用new關(guān)鍵字和Thread類或其子類建立一個(gè)線程對象后,該線程對象就處于新建狀態(tài)。它保持這個(gè)狀態(tài)直到程序start()這個(gè)線程。 -
就緒狀態(tài)
當(dāng)線程對象調(diào)用了start()方法之后,該線程就進(jìn)入了就緒狀態(tài)。就緒狀態(tài)的線程處于就緒隊(duì)列中,要等待JVM里線程調(diào)度器的調(diào)度。 -
運(yùn)行狀態(tài)
如果就緒狀態(tài)的線程獲取CPU的資源,就可以執(zhí)行run()方法,此時(shí)線程便處于運(yùn)行狀態(tài)。處于運(yùn)行狀態(tài)的線程有多種變化方式,它可以變?yōu)樽枞麪顟B(tài)、就緒狀態(tài)和死亡狀態(tài)。 -
阻塞狀態(tài)
如果一個(gè)線程執(zhí)行了sleep(睡眠)、suspend(掛起)等方法,失去所占有的資源后,該線程就從運(yùn)行狀態(tài)轉(zhuǎn)為阻塞狀態(tài)。在睡眠時(shí)間已到或獲得設(shè)備資源后可以重新進(jìn)入就緒狀態(tài)。可以分為三種:
1 . 等待阻塞:運(yùn)行狀態(tài)下的線程執(zhí)行wait()方法,使線程進(jìn)入到等待隊(duì)列。
2 . 同步阻塞:線程在獲得synchronized同步鎖失敗(因?yàn)橥芥i被其他線程占用)。
3 . 其他阻塞:通過調(diào)用線程的sleep() 或join()發(fā)出I/O請求時(shí),線程就會進(jìn)入阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí), 或join()等待線程終止或超時(shí),或者I/O處理完畢,線程重新轉(zhuǎn)入就緒狀態(tài)。 -
死亡狀態(tài)
一個(gè)運(yùn)行狀態(tài)的線程 完成任務(wù)或其他終止條件發(fā)生時(shí),該線程就切換到終止?fàn)顟B(tài)。
多線程02:創(chuàng)建線程:繼承Thread類
Java提供了三種創(chuàng)建線程的方法:
輔助幫助文檔:Class Thread
來源:Java Platform SE 8
Thread
三個(gè)步驟:1.自定義線程類Thread類;2.重寫run()方法,編寫線程結(jié)構(gòu)體;3.創(chuàng)建線程對象,調(diào)用start()方法啟動線程。
線程不一定立即執(zhí)行,CPU安排調(diào)度。
package lishizheng.demo01;//創(chuàng)建線程方式1:繼承Thread類,重寫run方法,調(diào)用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)建一個(gè)線程對象TestThread1 testThread1 = new TestThread1();//調(diào)用start方法開啟線程testThread1.start();for (int i = 0; i < 1000; i++) {System.out.println("我在學(xué)習(xí)多線程: " + i);}} }案例:下載圖片
首先下載名為commons-io的jar包:Download Apache Commons IO
解壓之后如下圖:
復(fù)制上圖中的jar包,在IDEA項(xiàng)目中新建文件夾lib,然后選中它,然后Ctrl+V,將jar包導(dǎo)入項(xiàng)目。
導(dǎo)入后,右擊lib文件夾,選擇Add As Library
彈出如下界面:點(diǎn)擊OK即可。
代碼如下:
package lishizheng.demo01;import org.apache.commons.io.FileUtils;import java.io.File; import java.io.IOException; import java.net.URL;//練習(xí)Thread,實(shí)現(xiàn)多線程下載圖片 public class TestThread2 extends Thread {private String url; //網(wǎng)絡(luò)圖片地址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)); //調(diào)用FileUtils類中的copyURLToFile方法實(shí)現(xiàn)下載} catch (IOException e) {e.printStackTrace();System.out.println("IO異常:downloader方法出現(xiàn)問題");}} } /* 執(zhí)行結(jié)果: 已下載文件名為:我的博客圖片01.png的圖片 已下載文件名為:我的博客圖片02.png的圖片 已下載文件名為:我的博客圖片03.png的圖片Process finished with exit code 0*/多線程03: 創(chuàng)建線程:實(shí)現(xiàn)Runnable接口
實(shí)現(xiàn)Runnable步驟:1.定義MyRunnable類實(shí)現(xiàn)Runnable接口;2.實(shí)現(xiàn)run()方法,編寫線程執(zhí)行體;3.創(chuàng)建線程對象,調(diào)用start()方法啟動線程。
創(chuàng)建一個(gè)線程,最簡單的方法是創(chuàng)建一個(gè)實(shí)現(xiàn) Runnable 接口的類。如下:
public class TestThread3 implements Runnable{}在創(chuàng)建一個(gè)實(shí)現(xiàn) Runnable 接口的類之后可以在類中實(shí)例化一個(gè)線程對象:
//創(chuàng)建Runnable接口的實(shí)現(xiàn)類對象TestThread3 testThread3 = new TestThread3();//創(chuàng)建線程對象,通過線程對象來開啟我們的線程,代理Thread thread = new Thread(testThread3);新線程創(chuàng)建之后,許喲啊調(diào)用它的 start() 方法它才會運(yùn)行:
thread.start();創(chuàng)建對象并調(diào)用start方法可以用一句代碼實(shí)現(xiàn):
new Thread(testThread3).start();//等價(jià)寫法全部代碼舉例如下:
package lishizheng.demo01;//創(chuàng)建線程方式2:實(shí)現(xiàn)Runnable接口,重寫run方法,執(zhí)行線程需要丟入實(shí)現(xiàn)Runnable接口的實(shí)現(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接口的實(shí)現(xiàn)類對象TestThread3 testThread3 = new TestThread3();//創(chuàng)建線程對象,通過線程對象來開啟我們的線程,代理Thread thread = new Thread(testThread3);thread.start();//等價(jià)寫法:// new Thread(testThread3).start();for (int i = 0; i < 1000; i++) {System.out.println("我在學(xué)習(xí)多線程: " + i);}} }/* 運(yùn)行代碼其中一部分:可以發(fā)現(xiàn)線程的執(zhí)行順序是由CPU來調(diào)度的!!!并不完全按照代碼書寫的順序。 我在學(xué)習(xí)多線程: 0 我在看代碼:0 我在學(xué)習(xí)多線程: 1 我在學(xué)習(xí)多線程: 2 我在學(xué)習(xí)多線程: 3 我在學(xué)習(xí)多線程: 4 我在學(xué)習(xí)多線程: 5 我在學(xué)習(xí)多線程: 6 我在學(xué)習(xí)多線程: 7 我在學(xué)習(xí)多線程: 8 我在學(xué)習(xí)多線程: 9 我在學(xué)習(xí)多線程: 10 我在學(xué)習(xí)多線程: 11 我在學(xué)習(xí)多線程: 12 我在學(xué)習(xí)多線程: 13 我在學(xué)習(xí)多線程: 14 我在學(xué)習(xí)多線程: 15 我在看代碼:1 我在學(xué)習(xí)多線程: 16 我在看代碼:2 我在學(xué)習(xí)多線程: 17 我在看代碼:3 我在學(xué)習(xí)多線程: 18*/小結(jié):
- 繼承Thread類
- 實(shí)現(xiàn)Runnable接口
案例:多線程操作同一資源對象
下面的代碼實(shí)現(xiàn)搶票的功能:共有10張火車票,三個(gè)人來搶。目的是學(xué)習(xí)并體會多線程對同一個(gè)資源對象操作的情況。
補(bǔ)充Thread.currentThread().getName()獲取線程的名字
package lishizheng.demo01;//多個(gè)線程同時(shí)操作同一個(gè)對象 //買火車票的例子//發(fā)現(xiàn)問題:多個(gè)線程操作同一個(gè)資源的情況下,線程不安全,數(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();// 一個(gè)對象,三個(gè)線程 new Thread(testThread4, "小明").start();new Thread(testThread4,"老師").start();new Thread(testThread4,"黃牛").start();} } /* 輸出結(jié)果如下: 黃牛拿到了第 10張票 老師拿到了第 8張票 小明拿到了第 9張票 老師拿到了第 6張票 黃牛拿到了第 7張票 老師拿到了第 4張票 小明拿到了第 5張票 老師拿到了第 2張票 黃牛拿到了第 3張票 小明拿到了第 1張票Process finished with exit code 0*/案例:龜兔賽跑
模擬龜兔賽跑,兩者在同一條跑道,先跑到100步者為勝利者。
用意:體會多線程競爭資源。
下面使用到下圖中的兩個(gè)方法:
來源:菜鳥教程
多線程04:創(chuàng)建線程:實(shí)現(xiàn)Callable接口
實(shí)現(xiàn)Callable接口的步驟:
利用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:實(shí)現(xiàn)Callable接口 /* * * */ public class TestCallable implements Callable<Boolean> {private String url; //網(wǎng)絡(luò)圖片地址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í)行服務(wù)ExecutorService ser = Executors.newFixedThreadPool(3);//提交執(zhí)行Future<Boolean> r1 = ser.submit(t1);Future<Boolean> r2 = ser.submit(t2);Future<Boolean> r3 = ser.submit(t3);//獲取結(jié)果boolean res1 = r1.get();boolean res2 = r2.get();boolean res3 = r3.get();//關(guān)閉服務(wù)ser.shutdown();} }//下載器 class WebDownLoader{//下載方法public void downloader(String url, String name){try {FileUtils.copyURLToFile(new URL(url), new File(name)); //調(diào)用FileUtils類中的copyURLToFile方法實(shí)現(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表達(dá)式
Lambda 表達(dá)式,也可稱為閉包,它是推動 Java 8 發(fā)布的最重要新特性。
Lambda 允許把函數(shù)作為一個(gè)方法的參數(shù)(函數(shù)作為參數(shù)傳遞進(jìn)方法中)。
使用 Lambda 表達(dá)式可以使代碼變的更加簡潔緊湊。
語法:
(parameters) -> expression 或 (parameters) -> {statements;}lambda表達(dá)式的重要特征:
可選類型聲明
不需要聲明參數(shù)類型,編譯器可以統(tǒng)一識別參數(shù)值。
可選的參數(shù)圓括號
一個(gè)參數(shù)無需定義圓括號,但多個(gè)參數(shù)需要定義圓括號
可選的大括號
如果主體包含了一個(gè)語句,就不需要使用大括號
可選的返回關(guān)鍵字
如果主體只有一個(gè)表達(dá)式返回值則編譯器會自動返回值,大括號需要指明表達(dá)式返回了一個(gè)數(shù)值。
函數(shù)式接口(Functional Interface)就是一個(gè)有且僅有一個(gè)抽象方法,但是可以有多個(gè)非抽象方法的接口。
對于函數(shù)式接口我們可以使用lambda表達(dá)式來創(chuàng)建該接口的對象。
//1.定義一個(gè)函數(shù)式接口 interface ILike{void lambda(); }//6.用lambda簡化ILike like5 = ()-> {System.out.println("I like Lambda5");};like5.lambda();/* 輸出:I like Lambda5 */下面是推導(dǎo)lambda表達(dá)式的過程,一步一步化簡,思路是先從用類實(shí)現(xiàn)接口,到靜態(tài)內(nèi)部類,到局部內(nèi)部類,再到匿名內(nèi)部類,一步步簡化,到最后是lambda表達(dá)式。
package lishizheng.demo03;//推導(dǎo)lambda表達(dá)式 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.定義一個(gè)函數(shù)式接口 interface ILike{void lambda(); }//2.實(shí)現(xiàn)類 class Like implements ILike{@Overridepublic void lambda() {System.out.println("I like Lambda");} }/* 輸出結(jié)果: I like Lambda I like Lambda2 I like Lambda3 I like Lambda4 I like Lambda5Process finished with exit code 0 */靜態(tài)代理模式
//真實(shí)對象和代理對象到實(shí)現(xiàn)同一個(gè)接口
//代理對象要代理真實(shí)對象
好處:代理對象可以做很多真實(shí)對象做不了的事情 ;真實(shí)對象專注于自己的事情
舉例:下面通過婚慶公司代理你來組織婚禮來說明一下靜態(tài)代理模式的功能。結(jié)婚之前要布置現(xiàn)場,然后主人結(jié)婚,結(jié)婚之后收尾款,這些方法都由婚慶公司對象來調(diào)用。
package lishizheng.demo04;//靜態(tài)代理模式//真實(shí)對象和代理對象到實(shí)現(xiàn)同一個(gè)接口 //代理對象到代理真實(shí)對象//好處:代理對象可以做很多真實(shí)對象做不了的事情 ;真實(shí)對象專注于自己的事情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("結(jié)婚,超開心");} }//代理角色 class WeddingCompany implements Marry{//代理誰? 真實(shí)目標(biāo)角色private Marry target;public WeddingCompany(Marry target) {this.target = target;}@Overridepublic void happyMarry() {before();this.target.happyMarry();//真實(shí)對象after();}private void after() {System.out.println("結(jié)婚之后收尾款");}private void before() {System.out.println("結(jié)婚之前布置現(xiàn)場");} } /* 輸出結(jié)果:結(jié)婚之前布置現(xiàn)場 結(jié)婚,超開心 結(jié)婚之后收尾款Process finished with exit code 0 */和多線程有什么關(guān)系呢?
復(fù)習(xí)Thread調(diào)用方法的時(shí)候,原理是一樣的,它本身是Runnable接口的代理。 下面通過婚慶公司和線程進(jìn)行對比。
public class StaticProxy {public static void main(String[] args) {You you = new You();//Thread代理真實(shí)的Runnable接口,new Thread(() -> System.out.println("我愛你") ).start();new WeddingCompany(you).happyMarry();} }多線程06:線程狀態(tài)
停止線程stop
- 不推薦使用JDK提供的stop方法,destroy方法
- 推薦讓線程自己停止下來,建議使用標(biāo)志位進(jìn)行終止變量:當(dāng)flag == false時(shí),線程終止。
舉例如下: 測試主線程和teststop線程的執(zhí)行過程,運(yùn)行期間讓teststop停止。
package lishizheng.demo05;//測試stop //1.建議線程正常停止:利用此時(shí),不建議死循環(huán) //2.建議使用標(biāo)志位 public class TestStop implements Runnable {// 1.設(shè)置一個(gè)標(biāo)志位private boolean flag = true;@Overridepublic void run() {int i = 0;while (flag){System.out.println("run...Thread: " + i++);}}//2.設(shè)置公開的方法停止線程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){//調(diào)用stop方法切換線程標(biāo)志位,讓線程停止testStop.stop();System.out.println("線程已停止");}}} }/* 輸出結(jié)果: 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指定當(dāng)前線程阻塞的毫秒數(shù),時(shí)間到后線程進(jìn)入就緒狀態(tài)
- sleep存在異常InterruptedException,要拋出
- sleep可以模擬網(wǎng)絡(luò)延遲,倒計(jì)時(shí)等
- 每個(gè)對象都有一個(gè)鎖,sleep不會釋放鎖。
舉例:sleep模擬網(wǎng)絡(luò)延時(shí),發(fā)現(xiàn)代碼的漏洞:這里是多線程操作同一個(gè)對象,造成線程不安全。通過sleep延時(shí)可以發(fā)現(xiàn)程序的執(zhí)行過程并不是我們預(yù)計(jì)的那樣。
package lishizheng.demo05;//模擬網(wǎng)絡(luò)延時(shí):放大問題的可能性,容易發(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) {//線程不安全:多個(gè)線程操作同一個(gè)對象TestSleep testThread4 = new TestSleep();new Thread(testThread4, "小明").start();new Thread(testThread4,"老師").start();new Thread(testThread4,"黃牛").start();} }/* 運(yùn)行結(jié)果: 黃牛拿到了第 9張票 老師拿到了第 8張票 小明拿到了第 10張票 老師拿到了第 7張票 小明拿到了第 7張票 黃牛拿到了第 6張票 黃牛拿到了第 5張票 小明拿到了第 4張票 老師拿到了第 5張票 小明拿到了第 3張票 黃牛拿到了第 1張票 老師拿到了第 2張票Process finished with exit code 0 */舉例
模擬倒計(jì)時(shí)和模擬獲取系統(tǒng)時(shí)間
線程禮讓yield
線程禮讓,讓當(dāng)前正在執(zhí)行的線程暫停,但不阻塞;將線程從運(yùn)行狀態(tài)轉(zhuǎn)為就緒狀態(tài)。讓CPU重新調(diào)度,禮讓不一定成功,看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相當(dāng)于線程插隊(duì),執(zhí)行完插隊(duì)線程才能繼續(xù)執(zhí)行別的線程。
測試:
觀察線程狀態(tài):
多線程07:線程的優(yōu)先級
每一個(gè) Java 線程都有一個(gè)優(yōu)先級,這樣有助于操作系統(tǒng)確定線程的調(diào)度順序。
Java 線程的優(yōu)先級是一個(gè)整數(shù),其取值范圍是 1 ~10(Thread.MIN_PRIORITY ~Thread.MAX_PRIORITY )。
默認(rèn)情況下,每一個(gè)線程都會分配一個(gè)優(yōu)先級 NORM_PRIORITY(5)。
具有較高優(yōu)先級的線程對程序更重要,并且應(yīng)該在低優(yōu)先級的線程之前分配處理器資源。但是,線程優(yōu)先級不能保證線程執(zhí)行的順序,而且非常依賴于平臺。 優(yōu)先級高的線程,先執(zhí)行的概率大。
測試線程優(yōu)先級
package lishizheng.demo05;public class TestPriority {public static void main(String[] args) {//主線程默認(rèn)優(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);//先設(shè)置優(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:守護(hù)線程(daemon)
守護(hù)線程是指為其他線程服務(wù)的線程。在JVM中,所有非守護(hù)線程都執(zhí)行完畢后,無論有沒有守護(hù)線程,虛擬機(jī)都會自動退出。
線程分為用戶線程和守護(hù)線程,虛擬機(jī)必須確保用戶線程執(zhí)行完畢,虛擬機(jī)不用等待守護(hù)線程執(zhí)行完畢,守護(hù)線程比如監(jiān)控內(nèi)存、垃圾回收等。
測試用例:守護(hù)線程thread.setDaemon(true);//默認(rèn)是false,表示是用戶線程會在JVM結(jié)束后接著運(yùn)行一段時(shí)間。
package lishizheng.demo05;//測試守護(hù)線程 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);//默認(rèn)是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");} }/* 部分運(yùn)行結(jié)果 已經(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í)行的先后順序。
線程同步:即當(dāng)有一個(gè)線程在對內(nèi)存進(jìn)行操作時(shí),其他線程都不可以對這個(gè)內(nèi)存地址進(jìn)行操作,直到該線程完成操作, 其他線程才能對該內(nèi)存地址進(jìn)行操作,而其他線程又處于等待狀態(tài),實(shí)現(xiàn)線程同步的方法有很多,臨界區(qū)對象就是其中一種。
多線程同時(shí)讀寫共享變量時(shí),會造成邏輯錯誤,因此需要通過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() {//模擬延時(shí)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)視器,因?yàn)橥椒椒ǖ耐奖O(jiān)視器就是this,就是這個(gè)對象本身或者是class
同步監(jiān)視器的執(zhí)行過程
擴(kuò)充指示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:死鎖
多個(gè)線程各自占有一些共享資源,并且互相等待其他線程占有的資源才能運(yùn)行,而導(dǎo)致兩個(gè)或多個(gè)線程都在等待對方釋放資源,都停止運(yùn)行的情形。某個(gè)同步塊擁有"兩個(gè)以上對象的鎖"時(shí),就可能發(fā)生死鎖。
死鎖舉例 :
灰姑娘和白雪公主都喜歡化妝,這里的化妝需要鏡子和口紅兩者都具備才能完成。 當(dāng)灰姑娘拿到口紅,而白雪拿到鏡子的時(shí)候,兩者相互等待,這樣就構(gòu)成死鎖。
程序進(jìn)入死鎖(卡死),如下圖:
測試代碼:
死鎖避免的方法
產(chǎn)生死鎖的四個(gè)必要條件:
只要想辦法破壞上面任意一個(gè)或者多個(gè)就可以避免死鎖。
Lock(鎖)
從JDK 5.0開始,Java提供了更強(qiáng)大的線程同步機(jī)制:通過顯式定義同步鎖對象來實(shí)現(xiàn)同步。同步鎖使用Lock對象充當(dāng)。java.util.concurrent.locks.Lock接口是控制多個(gè)線程對共享資源進(jìn)行訪問的工具。
鎖提供了對共享資源的獨(dú)占訪問,每次只能有1個(gè)對象對Lock對象加鎖,線程開始訪問共享資源之前要先獲得Lock對象。
ReentrantLock類實(shí)現(xiàn)了Lock,它擁有了與synchronized相同的并發(fā)性和內(nèi)存語義,在實(shí)現(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對比
多線程11:線程協(xié)作
生產(chǎn)者消費(fèi)者問題
生產(chǎn)者消費(fèi)者問題(英語:Producer-consumer problem),也稱有限緩沖問題(Bounded-buffer problem),是一個(gè)多進(jìn)程同步問題的經(jīng)典案例。該問題描述了共享固定大小緩沖區(qū)的兩個(gè)進(jìn)程——即所謂的“生產(chǎn)者”和“消費(fèi)者”——在實(shí)際運(yùn)行時(shí)會發(fā)生的問題。生產(chǎn)者的主要作用是生成一定量的數(shù)據(jù)放到緩沖區(qū)中,然后重復(fù)此過程。與此同時(shí),消費(fèi)者也在緩沖區(qū)消耗這些數(shù)據(jù)。該問題的關(guān)鍵就是要保證生產(chǎn)者不會在緩沖區(qū)滿時(shí)加入數(shù)據(jù),消費(fèi)者也不會在緩沖區(qū)中空時(shí)消耗數(shù)據(jù)。
來源:維基百科
線程通信
Java提供了幾個(gè)方法解決線程之間的通信問題
| wait() | 表示線程一直等待,直到其他線程的通知,會釋放鎖 |
| wait(long tiimeout) | 指定等待的毫秒數(shù) |
| notify() | 喚醒一個(gè)處于等待狀態(tài)的線程 |
| notifyAll() | 喚醒同一個(gè)對象上所有調(diào)用wait()方法的線程,優(yōu)先級別高的線程優(yōu)先被調(diào)度 |
注意:均為Object類的方法,都只能在同步方法或者同步代碼塊中使用,否則會拋出異常IllegalMonitorStateException.
解決方法1:管程法
生產(chǎn)者將生產(chǎn)好的數(shù)據(jù)放入緩沖區(qū),消費(fèi)者從緩沖區(qū)拿數(shù)據(jù)。
package lishizheng.demo06;//測試生產(chǎn)者消費(fèi)者,利用緩沖區(qū)解決,即管程法//what do we need? //生產(chǎn)者,消費(fèi)者,產(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));}} }//消費(fèi)者class Consumer extends Thread{SynContainer container;public Consumer( SynContainer container){this.container = container;}//消費(fèi)@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("消費(fèi)了第 " + container.pop().id + " 只雞");}} }//產(chǎn)品class Chicken{int id;public Chicken(int id) {this.id = id;} }//緩沖區(qū) class SynContainer{//容器大小10Chicken[] chickens = new Chicken[10];//容器計(jì)數(shù)器int count = 0;//生產(chǎn)者放入產(chǎn)品public synchronized void push(Chicken myChicken){//如果容器滿了,等待消費(fèi)者消費(fèi)if(count == 10){//通知消費(fèi)者消費(fèi),生產(chǎn)者等待try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//如果未滿,則放入產(chǎn)品chickens[count] = myChicken;count++;//進(jìn)程通信,通知 消費(fèi)者消費(fèi)this.notifyAll();}//消費(fèi)者消費(fèi)產(chǎn)品public synchronized Chicken pop(){//判斷能否消費(fèi)if(count == 0){//等待生產(chǎn)者生產(chǎn),消費(fèi)者等待try {wait();} catch (InterruptedException e) {e.printStackTrace();}}//如果可以消費(fèi)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
感謝您閱讀到最后,祝您一切順利。
總結(jié)
以上是生活随笔為你收集整理的Java多线程详解[狂神说Java]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java入门学习笔记[狂神说Java]
- 下一篇: python报错TabError: in