Java并發(fā)編程進(jìn)階之線程控制篇
一、線程的基本概念
1.并行和并發(fā)
并行:多個(gè)CPU核心同時(shí)工作,處理不同的任務(wù)。
并發(fā):多個(gè)任務(wù)交替使用 CPU 核心工作,以提高 CPU 利用率。
2.進(jìn)程和線程
進(jìn)程:程序的一次執(zhí)行。由操作系統(tǒng)創(chuàng)建并分配資源,執(zhí)行一個(gè)單獨(dú)的任務(wù)。
進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的獨(dú)立單位,每個(gè)進(jìn)程都有自己的內(nèi)存空間和系統(tǒng)資源。進(jìn)程內(nèi)所有線程共享堆存儲(chǔ)空間,保存程序中定義的對(duì)象和常量池。
Windows系統(tǒng)中,每個(gè)運(yùn)行的 Java 程序都是一個(gè)獨(dú)立的進(jìn)程。
線程:進(jìn)程內(nèi)的執(zhí)行單元,不分配單獨(dú)的資源,執(zhí)行一個(gè)單獨(dú)的子任務(wù)。
線程是進(jìn)程內(nèi)調(diào)度和分派的基本單位,共享進(jìn)程資源。每個(gè)線程有自己的獨(dú)立的棧存儲(chǔ)空間,保存線程執(zhí)行的方法以及基本類型的數(shù)據(jù)。
運(yùn)行的 Java 程序內(nèi)含至少一個(gè)主線程 main ,用戶可以在 Java 程序中自定義并調(diào)用多個(gè)線程。 JVM 垃圾回收線程也是一個(gè)獨(dú)立的線程。
二、線程的運(yùn)行狀態(tài)
線程除創(chuàng)建狀態(tài) New 和結(jié)束狀態(tài) Terminate 外,主要有以下幾種運(yùn)行狀態(tài):
①運(yùn)行(Running) :CPU 正在執(zhí)行線程。
②就緒(Runnable) :線程一切就緒,等待 CPU 執(zhí)行。
運(yùn)行/就緒狀態(tài) 統(tǒng)稱為可運(yùn)行狀態(tài) Runnable。 Java 程序中,線程在 運(yùn)行/就緒狀態(tài) 之間的切換由 JVM 自動(dòng)調(diào)度,開(kāi)發(fā)者無(wú)法獲知。線程之間的調(diào)度采用分優(yōu)先級(jí)多隊(duì)列時(shí)間片輪轉(zhuǎn)算法。進(jìn)程在執(zhí)行完 CPU 時(shí)間片切換到就緒狀態(tài)之前會(huì)先保存自己的狀態(tài),下次進(jìn)入運(yùn)行狀態(tài)時(shí)再重新加載。
③阻塞(Blocked) :線程因缺少其他資源,比如請(qǐng)求資源被上鎖而暫停執(zhí)行。在獲得資源后進(jìn)入就緒狀態(tài)。
④等待(Waitting) :線程接受了等待指令,釋放資源暫停執(zhí)行。在超時(shí)/接受喚醒指令后進(jìn)入就緒狀態(tài)。
三、線程操作實(shí)踐
Runnable 接口內(nèi)唯一聲明了 run 方法,由 Thread 類實(shí)現(xiàn)。開(kāi)發(fā)者在 run 方法中定義運(yùn)行時(shí)線程將要執(zhí)行的功能,線程開(kāi)啟后由Java虛擬機(jī)自動(dòng)調(diào)用并執(zhí)行。如果開(kāi)發(fā)者主動(dòng)調(diào)用 run 方法,則只會(huì)當(dāng)作普通方法執(zhí)行。
1.線程兩種定義方法
①繼承 Thread 類,重寫 run 方法。
public class MyThread extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
②實(shí)現(xiàn) Runnable 接口,實(shí)現(xiàn) run 方法。推薦使用,避免了單繼承的局限性。
public class MyThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
2.啟動(dòng)線程
Thread 類定義了 start 方法。調(diào)用 start 方法后,系統(tǒng)會(huì)開(kāi)啟一個(gè)新線程進(jìn)入就緒狀態(tài):由 JVM 會(huì)自動(dòng)對(duì)線程進(jìn)行調(diào)度,在運(yùn)行時(shí)調(diào)用并執(zhí)行線程的 run 方法。一個(gè)線程只能啟動(dòng)一次。
①如果自定義線程類繼承 Thread 類,直接啟動(dòng)。
public class Main { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread("ThreadName"); t1.start(); t2.start(); } }
②如果自定義線程類實(shí)現(xiàn) Runnable 接口,則需要借助 Thread 類啟動(dòng)線程。
public class Main { public static void main(String[] args) { MyThread mythread = new MyThread(); Thread t1 = new Thread(mythread); // 由系統(tǒng)指定默認(rèn)線程名 Thread-X Thread t2 = new Thread(mythread, "ThreadName"); // 開(kāi)發(fā)者自定義線程名 t1.start(); t2.start(); } }
3.同時(shí)定義和啟動(dòng)線程
通過(guò)匿名內(nèi)部類方式,我們可以實(shí)現(xiàn)同時(shí)定義和啟動(dòng)線程的簡(jiǎn)潔寫法。
public class Main { public static void main(String[] args) { Thread thread = new Thread(new Runnable(){ public void run(){ System.out.println(Thread.currentThread().getName()); } }).start(); } } class Test { public void method() { System.out.println(Thread.currentThread().getName()); } }
4.線程彈出與暫停
Thread 類定義了 yield 方法。當(dāng)前線程執(zhí)行到 Thread.yield()
方法,會(huì)停止運(yùn)行進(jìn)入就緒狀態(tài)。但線程切換到就緒狀態(tài)后,什么時(shí)候被 JVM 調(diào)度回運(yùn)行狀態(tài)開(kāi)發(fā)者無(wú)法控制。
public class ThreadDemo { public static void main(String[] args) { MyThread mythread = new MyThread(); Thread t1 = new Thread(mythread); Thread t2 = new Thread(mythread); t1.start(); t2.start(); } static class MyThread implements Runnable { @Override public void run() { int count = 0; for (int i = 0; i < 10000; i++) { Thread.yield(); // 切換到就緒狀態(tài) count++; System.out.println(count); } } } }
Thread 類定義了 sleep 方法。當(dāng)前線程執(zhí)行到 Thread.sleep(1000)
方法,會(huì)停止運(yùn)行進(jìn)入阻塞狀態(tài),但仍會(huì)保持對(duì)象鎖,其他線程不可訪問(wèn)其資源。直到超時(shí)后進(jìn)入就緒狀態(tài)。調(diào)用 sleep 方法需要捕獲或拋出 InterruptException 異常。
public class ThreadDemo { public static void main(String[] args) { MyThread mythread = new MyThread(); Thread t1 = new Thread(mythread); Thread t2 = new Thread(mythread); t1.start(); t2.start(); } static class MyThread implements Runnable { @Override public void run() { int count = 0; for (int i = 0; i < 10000; i++) { try{ Thread.sleep(1000); // 當(dāng)前線程暫停 1s } catch(InterruptException e){ e.printStackTrace(); } count++; System.out.println(count); } } } }
5.線程等待與喚醒
①線程等待
當(dāng)前線程執(zhí)行 obj.wait()
方法,線程會(huì)停止運(yùn)行并釋放對(duì)象鎖 obj,其他線程可以訪問(wèn)其資源。同時(shí)線程進(jìn)入 obj 對(duì)象的等待池,直到被 notify 方法喚醒進(jìn)入就緒狀態(tài)。調(diào)用 wait 方法需要捕獲或拋出 InterruptException 異常。
wait 方法允許計(jì)時(shí)等待。當(dāng)前線程執(zhí)行 obj.wait(1000)
方法,計(jì)時(shí)結(jié)束后線程會(huì)被自動(dòng)喚醒進(jìn)入就緒狀態(tài)。
②線程喚醒
當(dāng)前線程執(zhí)行 obj.notify()
方法,會(huì)隨機(jī)從 obj 對(duì)象等待池中選擇一個(gè)線程喚醒,使其進(jìn)入就緒狀態(tài)。但是 notify 方法不會(huì)釋放當(dāng)前進(jìn)程的對(duì)象鎖,如果該線程持有 obj 對(duì)象的鎖,當(dāng)前線程釋放鎖后被喚醒的其他線程才能被執(zhí)行。如果想被喚醒線程先執(zhí)行,notify 方法后添加 wait 方法釋放鎖。
當(dāng)前線程執(zhí)行 obj.notifyall()
方法,會(huì)將所有 obj 對(duì)象等待池中所有線程喚醒。
public class ThreadDemo { public static void main(String[] args) { MyThread t = new MyThread("t"); synchronized(t) { // 對(duì) t 設(shè)置對(duì)象鎖 try { t.start(); System.out.println("1"); t.wait(); // 當(dāng)前線程釋放 t 鎖,進(jìn)入 t 對(duì)象等待池 System.out.println("4"); } catch (InterruptedException e) { e.printStackTrace(); } } } static class MyThread extends Thread { @Override public void run() { synchronized (this) { // 對(duì) t 設(shè)置對(duì)象鎖 Thread.sleep(1000); System.out.println("2"); this.notify(); // 隨機(jī)喚醒一個(gè) t 對(duì)象等待池中的線程 System.out.println("3"); } } } }
6.線程中斷
調(diào)用 t.stop()
方法可以強(qiáng)制終止線程 t 運(yùn)行,但強(qiáng)制中斷線程可能會(huì)造成意想不到的問(wèn)題,已不推薦使用。
目前主要采用設(shè)置線程中斷標(biāo)志的方式,向線程發(fā)送中止信號(hào)。由線程自行終止運(yùn)行:
- 執(zhí)行
t.interrupt()
方法,將線程 t 中斷標(biāo)志設(shè)為 true 。 - 執(zhí)行
t.isInterrupted()
方法,查看線程 t 中斷標(biāo)志。 - 執(zhí)行
t.interrupted()
方法,查看線程 t 中斷標(biāo)志然后將其設(shè)為 false 。
public class ThreadDemo { public static void main(String[] args) { MyThread mythread = new MyThread(); Thread t = new Thread(mythread); t.start(); try { Thread.sleep(500); } catch (InterruptedException e) {} t.interrupt(); // 設(shè)置中斷標(biāo)志為 true } static class MyThread implements Runnable { @Override public void run() { while (true) { System.out.print("hello"); if(this.isInterrupted()){ // 查看中斷標(biāo)志,若為 true 結(jié)束循環(huán) break; } } } } }
到此這篇關(guān)于Java并發(fā)編程進(jìn)階之線程控制篇的文章就介紹到這了,更多相關(guān)Java線程控制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談Zookeeper開(kāi)源客戶端框架Curator
這篇文章主要介紹了淺談Zookeeper開(kāi)源客戶端框架Curator的相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下。2017-10-10Java中List對(duì)象集合按對(duì)象中某字段進(jìn)行排序舉例
這篇文章主要給大家介紹了關(guān)于Java中List對(duì)象集合按對(duì)象中某字段進(jìn)行排序的相關(guān)資料,我們?cè)谌粘i_(kāi)發(fā)中也經(jīng)常會(huì)用到排序算法,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07Java數(shù)據(jù)結(jié)構(gòu)之優(yōu)先級(jí)隊(duì)列(PriorityQueue)用法詳解
優(yōu)先級(jí)隊(duì)列是一種先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),操作的數(shù)據(jù)帶有優(yōu)先級(jí),這種數(shù)據(jù)結(jié)構(gòu)就是優(yōu)先級(jí)隊(duì)列(PriorityQueue)。本文將詳細(xì)講講Java優(yōu)先級(jí)隊(duì)列的用法,感興趣的可以了解一下2022-07-07Mybatis的動(dòng)態(tài)Sql組合模式詳情
這篇文章主要介紹了Mybatis的動(dòng)態(tài)Sql組合模式詳情,這篇文章從組合模式的角度分析了Mybatis動(dòng)態(tài)sql的部分,SqlNode是組合模式的Component接口,更多相關(guān)內(nèi)容需要的小伙伴可以參考一下2022-08-08Idea?編譯并運(yùn)行?Spark?3.1.1?源碼的方法
這篇文章主要介紹了Idea?編譯并運(yùn)行?Spark?3.1.1源碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-11-11feign調(diào)用中文參數(shù)被encode編譯的問(wèn)題
這篇文章主要介紹了feign調(diào)用中文參數(shù)被encode編譯的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03SpringBoot Admin使用及心跳檢測(cè)原理分析
這篇文章主要介紹了SpringBoot Admin使用及心跳檢測(cè)原理分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11基于Spring接口集成Caffeine+Redis兩級(jí)緩存
這篇文章主要介紹了基于Spring接口集成Caffeine+Redis兩級(jí)緩存,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-07-07