Java多線(xiàn)程Thread類(lèi)的使用及注意事項(xiàng)
Thread類(lèi)的基本用法
創(chuàng)建子類(lèi),繼承自Thread并且重寫(xiě)run方法:
class MyThread extends Thread { @Override public void run() { System.out.println("hello thread"); } } public class Demo1 { public static void main(String[] args) { // 最基本的創(chuàng)建線(xiàn)程的辦法. Thread t = new MyThread(); //調(diào)用了start方法才是真正的在系統(tǒng)中創(chuàng)建了線(xiàn)程,執(zhí)行run方法 t.start(); } }
創(chuàng)建一個(gè)類(lèi),實(shí)現(xiàn)Runnable接口再創(chuàng)建Runnable是實(shí)例傳給Thread
class MyRunnable implements Runnable{ @Override public void run() { System.out.println("hello"); } } public class Demo3 { public static void main(String[] args) { Thread t = new Thread(new MyRunnable()); t.start(); } }
匿名內(nèi)部類(lèi):
創(chuàng)建了一個(gè)匿名內(nèi)部類(lèi),繼承自Thread類(lèi),同時(shí)重寫(xiě)run方法,再new出匿名內(nèi)部類(lèi)的實(shí)例
public class Demo4 { public static void main(String[] args) { Thread t = new Thread(){ @Override public void run() { System.out.println("hello"); } }; t.start(); } }
new的Runnable,針對(duì)這個(gè)創(chuàng)建的匿名內(nèi)部類(lèi),同時(shí)new出的Runnable實(shí)例傳給Thread的構(gòu)造方法
public class Demo5 { public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println("hello"); } }); t.start(); } }
lambda表達(dá)式 lambda代替Runnable
public class Demo6 { public static void main(String[] args) { Thread t = new Thread(() ->{ System.out.println("hello"); }); t.start(); } }
線(xiàn)程指標(biāo)
- 1.isDaemon();是否后臺(tái)線(xiàn)程 后臺(tái)線(xiàn)程不影響進(jìn)程退出,不是后臺(tái)線(xiàn)程會(huì)影響進(jìn)程退出
- 2.isAlive();是否存活 在調(diào)用start前系統(tǒng)中是沒(méi)有對(duì)應(yīng)線(xiàn)程的,run方法執(zhí)行完后線(xiàn)程就銷(xiāo)毀了,t對(duì)象可能還存在
- 3.isinterrupted();是否被中斷
run和start的區(qū)別:run單純的只是一個(gè)普通方法描述了任務(wù)的內(nèi)容 start則是一個(gè)特殊的方法,內(nèi)部會(huì)在系統(tǒng)中創(chuàng)建線(xiàn)程
中斷線(xiàn)程
線(xiàn)程停下來(lái)的關(guān)鍵是要讓對(duì)應(yīng)run方法執(zhí)行完,對(duì)于main線(xiàn)程來(lái)說(shuō)main方法執(zhí)行完了才會(huì)終止
1.手動(dòng)設(shè)置標(biāo)志位
在線(xiàn)程中控制這個(gè)標(biāo)志位就能影響到這個(gè)線(xiàn)程結(jié)束,但是此處多個(gè)線(xiàn)程共用一片虛擬空間,因此main線(xiàn)程修改的isQuit和t線(xiàn)程判斷的isQuit是同一個(gè)值
public class Demo10 { // 通過(guò)這個(gè)變量來(lái)控制線(xiàn)程是否結(jié)束. private static boolean isQuit = false; public static void main(String[] args) { Thread t = new Thread(() -> { while (!isQuit) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); // 就可以在 main 線(xiàn)程中通過(guò)修改 isQuit 的值, 來(lái)影響到線(xiàn)程是否退出 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // main 線(xiàn)程在 5s 之后, 修改 isQuit 的狀態(tài). isQuit = true; } }
2.使用Thread中內(nèi)置的一個(gè)標(biāo)志位來(lái)判定
Thread.interruted()這是一個(gè)靜態(tài)方法 Thread.currentThread().isInterrupted()這是一個(gè)實(shí)例方法,其中currentThread能夠獲取到當(dāng)前線(xiàn)程的實(shí)例
public class Demo7 { public static void main(String[] args) { Thread t = new Thread(() -> { while(!Thread.currentThread().isInterrupted()){ System.out.println("hello"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); // 當(dāng)觸發(fā)異常之后, 立即就退出循環(huán)~ System.out.println("這是收尾工作"); break; } } }); t.start(); try{ Thread.sleep(5000); }catch (InterruptedException e){ e.printStackTrace(); } // 在主線(xiàn)程中, 調(diào)用 interrupt 方法, 來(lái)中斷這個(gè)線(xiàn)程. // t.interrupt 的意思就是讓 t 線(xiàn)程被中斷!! t.interrupt(); } }
需要注意的是調(diào)用這個(gè)方法t.interrupt()可能會(huì)產(chǎn)生兩種情況:
- 1)如果t線(xiàn)程處在就緒就設(shè)置線(xiàn)程的標(biāo)志位為true
- 2)如果t線(xiàn)程處在阻塞狀態(tài)(sleep),就會(huì)觸發(fā)一個(gè)InterruptExeception
線(xiàn)程等待
多個(gè)線(xiàn)程之間調(diào)度順序是不確定的,有時(shí)候我們需要控制線(xiàn)程之間的順序,線(xiàn)程等待就是一種控制線(xiàn)程執(zhí)行順序的手段,此處的線(xiàn)程等待只要是控制線(xiàn)程結(jié)束的先后順序。
哪個(gè)線(xiàn)程中的join,哪個(gè)線(xiàn)程就會(huì)阻塞等待直到對(duì)應(yīng)的線(xiàn)程執(zhí)行完畢為止。
- t.join();調(diào)用這個(gè)方法的線(xiàn)程是main線(xiàn)程,針對(duì)t這個(gè)對(duì)象調(diào)用的此時(shí)就是讓main等待t。代碼執(zhí)行到j(luò)oin這一行就停下了,讓t先結(jié)束然后main繼續(xù)。
- t.join(10000);join提供了另一個(gè)版本為帶一個(gè)參數(shù)的,參數(shù)為等待時(shí)間10s之后join直接返回不再等待
Thread.currentThread()
能夠獲取當(dāng)前線(xiàn)程的應(yīng)用,哪個(gè)線(xiàn)程調(diào)用的currentThread就獲取到哪個(gè)線(xiàn)程的實(shí)例 對(duì)比this如下:
對(duì)于這個(gè)代碼來(lái)說(shuō),通過(guò)繼承Thread的方法來(lái)創(chuàng)建線(xiàn)程。此時(shí)run方法中直接通過(guò)this拿到的就是當(dāng)前Thread的實(shí)例
public class Demo4 { public static void main(String[] args) { Thread t = new Thread(){ @Override public void run() { System.out.println(Thread.currentThread().getName()); System.out.println(this.getName()); } }; t.start(); } }
然而此處this不是指向Thread類(lèi)型,而是指向Runnable,Runnable只是一個(gè)單純的任務(wù)沒(méi)有name屬性,要想拿到線(xiàn)程名字只能通過(guò)Thread.currentThread()
public class Demo5 { public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { //err //System.out.println(this.getName()); //right System.out.println(Thread.currentThread().getName()); } }); t.start(); } }
進(jìn)程狀態(tài)
針對(duì)系統(tǒng)層面:
- 就緒
- 阻塞
java中Thread類(lèi)進(jìn)一步細(xì)化:
- NEW:把Thread對(duì)象創(chuàng)建好了但是還沒(méi)有調(diào)用start
- TERMINATED:操作系統(tǒng)中的線(xiàn)程已執(zhí)行完畢銷(xiāo)毀,但是Thread對(duì)象還在獲取到的狀態(tài)
- RUNNABLE:就緒狀態(tài),處在該狀態(tài)的線(xiàn)程就是在就緒隊(duì)列中,隨時(shí)可以調(diào)度到CPU上
- TIME_WAITING:調(diào)用了sleep就會(huì)進(jìn)入到該狀態(tài),join(超時(shí)時(shí)間) BLOCKED:當(dāng)前線(xiàn)程在等待鎖導(dǎo)致了阻塞
- WAITING:當(dāng)前線(xiàn)程在等待喚醒
狀態(tài)轉(zhuǎn)換圖:
線(xiàn)程安全問(wèn)題
定義:操作系統(tǒng)中線(xiàn)程調(diào)度是隨機(jī)的,導(dǎo)致程序的執(zhí)行可能會(huì)出現(xiàn)一些bug。如果因?yàn)檎{(diào)度隨機(jī)性引入了bug線(xiàn)程就是不安全的,反之則是安全的。
解決方法:加鎖,給方法直接加上synchronized關(guān)鍵字,此時(shí)進(jìn)入方法就會(huì)自動(dòng)加鎖,離開(kāi)方法就會(huì)自動(dòng)解鎖。當(dāng)一個(gè)線(xiàn)程加鎖成功的時(shí)候,其他線(xiàn)程嘗試加鎖就會(huì)觸發(fā)阻塞等待,阻塞會(huì)一直持續(xù)到占用鎖的線(xiàn)程把鎖釋放為止。
synchronized public void increase() { count++; }
線(xiàn)程不安全產(chǎn)生的原因:
- 1.線(xiàn)程是搶占式執(zhí)行,線(xiàn)程間的調(diào)度充滿(mǎn)隨機(jī)性。
- 2.多個(gè)線(xiàn)程對(duì)同一個(gè)變量進(jìn)行修改操作
- 3.針對(duì)變量的操作不是原子的
- 4.內(nèi)存可見(jiàn)性也會(huì)影響線(xiàn)程安全(針對(duì)同一個(gè)變量t1線(xiàn)程循環(huán)進(jìn)行多次讀操作,t2線(xiàn)程少次修改操作,t1就不會(huì)從內(nèi)存讀數(shù)據(jù)了而是從寄存器里讀)
- 5.指令重排序,也是編譯器優(yōu)化的一種操作,保證邏輯不變的情況下調(diào)整順序,解決方法synchronized。
內(nèi)存可見(jiàn)性解決方法:
- 1.使用synchronized關(guān)鍵字 使用synchronized不光能保證指令的原子性,同時(shí)也能保證內(nèi)存的可見(jiàn)性。被synchronized包裹起來(lái)的代碼編譯器就不會(huì)從寄存器里讀。
- 2.使用volatile關(guān)鍵字 能夠保證內(nèi)存可見(jiàn)性,禁止編譯器作出上述優(yōu)化,編譯器每次執(zhí)行判定相等都會(huì)重新從內(nèi)存讀取。
synchronized用法
在java中每個(gè)類(lèi)都是繼承自O(shè)bject,每個(gè)new出來(lái)的實(shí)例里面一方面包含自己安排的屬性,另一方面包含了“對(duì)象頭”即對(duì)象的一些元數(shù)據(jù)。加鎖操作就是在這個(gè)對(duì)象頭里面設(shè)置一個(gè)標(biāo)志位。
1.直接修飾普通的方法
使用synchronized的時(shí)候本質(zhì)上是對(duì)某個(gè)“對(duì)象”進(jìn)行加鎖,此時(shí)的鎖對(duì)象就是this。加鎖操作就是在設(shè)置this的對(duì)象頭的標(biāo)志位,當(dāng)兩個(gè)線(xiàn)程同時(shí)嘗試對(duì)同一個(gè)對(duì)象加鎖的時(shí)候才有競(jìng)爭(zhēng),如果是兩個(gè)線(xiàn)程在針對(duì)兩個(gè)不同對(duì)象加鎖就沒(méi)有競(jìng)爭(zhēng)。
class Counter{ public int count; synchronized public void increase(){ count++; } }
2.修飾一個(gè)代碼塊
需要顯示制定針對(duì)那個(gè)對(duì)象加鎖(java中的任意對(duì)象都可以作為鎖對(duì)象)
public void increase(){ synchronized(this){ count++; } }
3.修飾一個(gè)靜態(tài)方法
相當(dāng)于針對(duì)當(dāng)前類(lèi)的類(lèi)對(duì)象加鎖,類(lèi)對(duì)象就是運(yùn)行程序的時(shí)候。class文件被加載到JVM內(nèi)存中的模樣。
synchronized public static void func(){ }
或者
public static void func(){ synchronized(Counter.class){ } }
監(jiān)視器鎖monitor lock
可重入鎖就是同一個(gè)線(xiàn)程針對(duì)同一個(gè)鎖,連續(xù)加鎖兩次,如果出現(xiàn)死鎖就是不可重入鎖,如果不會(huì)死鎖就是可重入的。因此就把synchronized實(shí)現(xiàn)為可重入鎖,下面的例子里啊連續(xù)加鎖操作不會(huì)導(dǎo)致死鎖??芍厝腈i內(nèi)部會(huì)記錄所被哪個(gè)線(xiàn)程占用也會(huì)記錄加鎖次數(shù),因此后續(xù)再加鎖就不是真的加鎖而是單純地把技術(shù)給自增。
synchronized public void increase(){ synchronized(this){ count++; } }
死鎖的其他場(chǎng)景
- 1.一個(gè)線(xiàn)程一把鎖
- 2.兩個(gè)線(xiàn)程兩把鎖
- 3.N個(gè)線(xiàn)程M把鎖(哲學(xué)家就餐問(wèn)題,解決方法:先拿編號(hào)小的筷子)
死鎖的四個(gè)必要條件(前三個(gè)都是鎖本身的特點(diǎn))
- 1.互斥使用,一個(gè)鎖被另一個(gè)線(xiàn)程占用后其他線(xiàn)程就用不了(鎖的本質(zhì),保證原子性)
- 2.不可搶占,一個(gè)鎖被一個(gè)線(xiàn)程占用后其他線(xiàn)程不可把這個(gè)鎖給挖走
- 3.請(qǐng)求和保持,當(dāng)一個(gè)線(xiàn)程占據(jù)了多把鎖之后,除非顯示的釋放否則這些鎖中都是該線(xiàn)程持有的
- 4.環(huán)路等待,等待關(guān)系成環(huán)(解決:遵循固定的順序加鎖就不會(huì)出現(xiàn)環(huán)路等待)
java線(xiàn)程類(lèi):
- 不安全的:ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet,StringBuilder
- 安全的:Vector,HashTable,ConcurrentHashMap,StringBuffer,String
volatile
禁止編譯器優(yōu)化保證內(nèi)存可見(jiàn)性,產(chǎn)生原因:計(jì)算機(jī)想執(zhí)行一些計(jì)算就需要把內(nèi)存的數(shù)據(jù)讀到CPU寄存器中,然后再?gòu)募拇嫫髦杏?jì)算寫(xiě)回到內(nèi)存中,因?yàn)镃PU訪(fǎng)問(wèn)寄存器的速度比訪(fǎng)問(wèn)內(nèi)存快很多,當(dāng)CPU連續(xù)多次訪(fǎng)問(wèn)內(nèi)存結(jié)果都一樣,CPU就會(huì)選擇訪(fǎng)問(wèn)寄存器。
JMM(Java Memory Model)Java內(nèi)存模型
就是把硬件結(jié)構(gòu)在java中用專(zhuān)業(yè)的術(shù)語(yǔ)又重新抽象封裝了一遍。
- 工作內(nèi)存(work memory)其實(shí)指的不是內(nèi)存,而是CPU寄存器。
- 主內(nèi)存(main memeory)這才是主內(nèi)存。
- 原因:java作為一個(gè)跨平臺(tái)編程語(yǔ)言要把硬件細(xì)節(jié)封裝起來(lái),假設(shè)某個(gè)計(jì)算機(jī)沒(méi)有CPU或者內(nèi)存同樣可以套到上述模型中。
寄存器,緩存和內(nèi)存之間的關(guān)系
CPU從內(nèi)存取數(shù)據(jù)太慢,因此把數(shù)據(jù)直接放到寄存器里來(lái)讀,但寄存器空間太緊張于是又搞了一個(gè)存儲(chǔ)空間,比寄存器大比內(nèi)存小速度比寄存器慢比內(nèi)存快稱(chēng)為緩存。寄存器和緩存統(tǒng)稱(chēng)為工作內(nèi)存。
寄存器,緩存和內(nèi)存之間的關(guān)系圖
- 存儲(chǔ)空間:CPU<L1<L2<L3<內(nèi)存
- 速度:CPU>L1>L2>L3>內(nèi)存
- 成本:CPU>L1>L2>L3>內(nèi)存
volatile和synchronized的區(qū)別
- volatile只是保證可見(jiàn)性不保證原子性,只是處理一個(gè)線(xiàn)程讀和一個(gè)線(xiàn)程寫(xiě)的過(guò)程。
- synchronized都能處理
wait和notify
等待和通知處理線(xiàn)程調(diào)度隨機(jī)性問(wèn)題的,join也是一種控制順序的方式更傾向于控制線(xiàn)程結(jié)束。wait和notify都是Object對(duì)象的方法,調(diào)用wait方法的線(xiàn)程就會(huì)陷入阻塞,阻塞到有線(xiàn)程通過(guò)notify來(lái)通知。
public class Demo9 { public static void main(String[] args) throws InterruptedException { Object object = new Object(); System.out.println("wait前"); object.wait(); System.out.println("wait后"); } }
wait內(nèi)部會(huì)做三件事;
- 1.先釋放鎖
- 2.等待其他線(xiàn)程的通知
- 3.收到通知后重新獲得鎖并繼續(xù)往下執(zhí)行
因此想用wait/notify就得搭配synchronized
public class Demo9 { public static void main(String[] args) throws InterruptedException { Object object = new Object(); synchronized (object){ System.out.println("wait前"); object.wait(); System.out.println("wait后"); } } }
注意:wait notify都是針對(duì)同一對(duì)象來(lái)操作的,例如現(xiàn)在有一個(gè)對(duì)象o,有10個(gè)線(xiàn)程都調(diào)用了o.wait,此時(shí)10個(gè)線(xiàn)程都是阻塞狀態(tài)。如果調(diào)用了o.notify就會(huì)把10個(gè)線(xiàn)程中的一個(gè)線(xiàn)程喚醒。而notifyAll就會(huì)把所有10個(gè)線(xiàn)程全都給喚醒,此時(shí)就會(huì)競(jìng)爭(zhēng)鎖。
到此這篇關(guān)于Java多線(xiàn)程Thread類(lèi)的使用及注意事項(xiàng)的文章就介紹到這了,更多相關(guān)Java Thread 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java多線(xiàn)程ThreadPoolExecutor詳解
- Java中使用Thread類(lèi)和Runnable接口實(shí)現(xiàn)多線(xiàn)程的區(qū)別
- Java超詳細(xì)講解多線(xiàn)程中的Process與Thread
- Java多線(xiàn)程 ThreadLocal原理解析
- Java多線(xiàn)程ThreadAPI詳細(xì)介紹
- Java使用Thread創(chuàng)建多線(xiàn)程并啟動(dòng)操作示例
- java多線(xiàn)程Thread的實(shí)現(xiàn)方法代碼詳解
- java 多線(xiàn)程Thread與runnable的區(qū)別
- Java多線(xiàn)程Thread類(lèi)的使用詳解
相關(guān)文章
基于mybatis注解動(dòng)態(tài)sql中foreach工具的方法
這篇文章主要介紹了mybatis注解動(dòng)態(tài)sql中foreach工具方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Java實(shí)現(xiàn)獲取圖片和視頻文件的Exif信息
這篇文章將重點(diǎn)為大家介紹一下如何使用Java編程語(yǔ)言結(jié)合metadata-extractor去自動(dòng)獲取全景圖片的Exif信息,獲取照片的拍攝坐標(biāo)信息,感興趣的可以了解一下2022-11-11搭建maven私有倉(cāng)庫(kù)的方法實(shí)現(xiàn)
Maven是一個(gè)流行的Java項(xiàng)目管理工具,它可以幫助我們管理項(xiàng)目的構(gòu)建、報(bào)告和文檔,本文主要介紹了搭建maven私有倉(cāng)庫(kù)的方法實(shí)現(xiàn),感興趣的可以了解一下2023-05-05Java使用JMeter進(jìn)行高并發(fā)測(cè)試
軟件的壓力測(cè)試是一種保證軟件質(zhì)量的行為,本文主要介紹了Java使用JMeter進(jìn)行高并發(fā)測(cè)試,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11SpringBoot中實(shí)現(xiàn)@Scheduled動(dòng)態(tài)定時(shí)任務(wù)
SpringBoot中的@Scheduled注解為定時(shí)任務(wù)提供了一種很簡(jiǎn)單的實(shí)現(xiàn),本文主要介紹了SpringBoot中實(shí)現(xiàn)@Scheduled動(dòng)態(tài)定時(shí)任務(wù),具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01Java的線(xiàn)程阻塞、中斷及優(yōu)雅退出方法詳解
這篇文章主要介紹了Java的線(xiàn)程阻塞、中斷及優(yōu)雅退出方法詳解,Java中的線(xiàn)程阻塞是指當(dāng)一個(gè)線(xiàn)程無(wú)法繼續(xù)執(zhí)行時(shí),它會(huì)進(jìn)入阻塞狀態(tài),直到某個(gè)條件滿(mǎn)足后才能繼續(xù)執(zhí)行,線(xiàn)程阻塞可以通過(guò)多種方式實(shí)現(xiàn),需要的朋友可以參考下2023-10-10使用maven插件對(duì)java工程進(jìn)行打包過(guò)程解析
這篇文章主要介紹了使用maven插件對(duì)java工程進(jìn)行打包過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08