詳解JUC并發(fā)編程中的進(jìn)程與線程學(xué)習(xí)
進(jìn)程與線程
進(jìn)程
- 程序由指令和數(shù)據(jù)組成,但這些指令要運(yùn)行,數(shù)據(jù)要讀寫,就必須將指令加載至 CPU,數(shù)據(jù)加載至內(nèi)存。在指令運(yùn)行過(guò)程中還需要用到磁盤、網(wǎng)絡(luò)等設(shè)備。進(jìn)程就是用來(lái)加載指令、管理內(nèi)存、管理 IO 的
- 當(dāng)一個(gè)程序被運(yùn)行,從磁盤加載這個(gè)程序的代碼至內(nèi)存,這時(shí)就開(kāi)啟了一個(gè)進(jìn)程。
- 進(jìn)程就可以視為程序的一個(gè)實(shí)例。大部分程序可以同時(shí)運(yùn)行多個(gè)實(shí)例進(jìn)程(例如記事本、畫圖、瀏覽器等),也有的程序只能啟動(dòng)一個(gè)實(shí)例進(jìn)程(例如網(wǎng)易云音樂(lè)、360 安全衛(wèi)士等)
線程
線程是主要負(fù)責(zé)運(yùn)行指令,進(jìn)程是主要管加載指令。
一個(gè)進(jìn)程之內(nèi)可以分為一到多個(gè)線程。
一個(gè)線程就是一個(gè)指令流,將指令流中的一條條指令以一定的順序交給 CPU 執(zhí)行
Java 中,線程作為最小調(diào)度單位,進(jìn)程作為資源分配的最小單位。 在 windows 中進(jìn)程是不活動(dòng)的,只是作為線程的容器
同步異步
- 需要等待結(jié)果返回,才能繼續(xù)運(yùn)行就是同步
- 不需要等待結(jié)果返回,就能繼續(xù)運(yùn)行就是異步
串行并行執(zhí)行時(shí)間
使用多核cpu并行執(zhí)行可以明顯的提高執(zhí)行效率
- 串行執(zhí)行時(shí)間 = 各個(gè)線程時(shí)間累加和 + 匯總時(shí)間
- 并行執(zhí)行時(shí)間 = 最慢的線程時(shí)間 + 匯總時(shí)間
注意:?jiǎn)魏艘廊皇遣l(fā)的思想(即:cpu輪流去執(zhí)行線程,微觀上仍舊是串行),使用單核的多線程可能會(huì)比使用單核的單線程慢,這是因?yàn)槎嗑€程上下文切換反而浪費(fèi)了時(shí)間。
創(chuàng)建和運(yùn)行線程
1.使用 Thread
public static void main(String[] args) { // 創(chuàng)建線程對(duì)象 Thread t = new Thread("線程1") { public void run() { // 要執(zhí)行的任務(wù) log.debug("線程1被啟動(dòng)了"); } }; // 啟動(dòng)線程 t.start(); log.debug("測(cè)試"); }
2.使用 Runnable 配合 Thread
public static void main(String[] args) { Runnable runnable = new Runnable() { public void run() { // 要執(zhí)行的任務(wù) log.debug("線程1被啟動(dòng)了"); } }; // 創(chuàng)建線程對(duì)象 Thread t = new Thread(runnable); t.setName("線程1"); // 啟動(dòng)線程 t.start(); log.debug("測(cè)試"); }
這里的Runnable是一個(gè)接口,接口中只有一個(gè)抽象方法,由我們來(lái)提供實(shí)現(xiàn),實(shí)現(xiàn)中包含線程的代碼就可以了。
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
Thread 與 Runnable 的關(guān)系原理分析
方法1原理分析
方法2是使用runnable對(duì)象,當(dāng)成參數(shù)傳給Thread構(gòu)造方法,其中又調(diào)用了init方法,下面是Thread構(gòu)造方法的源碼
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
繼續(xù)跟蹤查看runnable對(duì)象傳到哪里去了,可以看到又傳給了另一個(gè)重載的init,如下
private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); }private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); }
再次跟蹤可以看到是把runnable對(duì)象傳給了一個(gè)thread的一個(gè)成員變量
//省略部分代碼 this.target = target;
那么這個(gè)成員變量在哪里在使用了呢,經(jīng)過(guò)查找可以發(fā)現(xiàn)是在run方法里面,只不過(guò)Thread發(fā)現(xiàn)有runnable對(duì)象就會(huì)先采用runnable的run方法。
@Override public void run() { if (target != null) { target.run(); } }
方法2原理分析
通過(guò)創(chuàng)建一個(gè)子類去重寫Thread類的run方法,這樣就不會(huì)執(zhí)行父類的run方法。
1.用 Runnable 更容易與線程池等高級(jí) API 配合
2.用 Runnable 讓任務(wù)類脫離了 Thread 繼承體系,更靈活
方法3 FutureTask配合Thread創(chuàng)建線程
Future接口中含有g(shù)et方法來(lái)返回結(jié)果的
//省略部分代碼 V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
而runnable本身是沒(méi)有返回結(jié)果的,runnable不能將結(jié)果傳給其他線程。
public interface Runnable { public abstract void run(); }
要注意到FutureTask也實(shí)現(xiàn)了Runnable接口,也可以傳給Thread的有參構(gòu)造里面。
創(chuàng)建線程的代碼
public static void main(String[] args) throws ExecutionException, InterruptedException { // 創(chuàng)建任務(wù)對(duì)象 FutureTask<Integer> task3 = new FutureTask<>(new Callable<Integer>() { @Override public Integer call() throws Exception { log.debug("線程1被執(zhí)行了"); return 666; } }); // 參數(shù)1 是任務(wù)對(duì)象; 參數(shù)2 是線程名字,推薦 new Thread(task3, "線程1").start(); // 主線程阻塞,同步等待 task 執(zhí)行完畢的結(jié)果 Integer result = task3.get(); log.debug("結(jié)果是:{}", result); }
查看進(jìn)程
- 任務(wù)管理器可以查看進(jìn)程和線程數(shù),也可以用來(lái)殺死進(jìn)程,也可以在控制臺(tái)使用
tasklist
查看進(jìn)程taskkill
殺死進(jìn)程 - jconsole 遠(yuǎn)程監(jiān)控配置來(lái)查看
線程運(yùn)行原理
JVM 中由堆、棧、方法區(qū)所組成,其中棧就是給線程使用的。
方法調(diào)用時(shí),就會(huì)對(duì)該方法產(chǎn)生一個(gè)棧幀,方法的局部變量都會(huì)在棧幀中存儲(chǔ)。棧是后進(jìn)先出,當(dāng)method2執(zhí)行完就會(huì)回收,在執(zhí)行完同時(shí)會(huì)記錄返回地址,然后在method1中繼續(xù)執(zhí)行。
線程之間的棧幀是相互獨(dú)立的,之間互不干擾。
線程上下文切換
當(dāng)上下文切換時(shí),要保存當(dāng)前的狀態(tài),因?yàn)榭赡苁菚r(shí)間片用完了,此時(shí)線程還沒(méi)有結(jié)束。Java中對(duì)應(yīng)的就是程序計(jì)數(shù)器
start與run方法
啟動(dòng)一個(gè)線程必須要用start方法,如果直接調(diào)用類里面的run方法實(shí)際走的是main主線程。
線程start前getState()
得到的是NEW
線程start后getState()
得到的是RUNNABLE
public static void main(String[] args) { Thread t1 = new Thread("t1") { @Override public void run() { log.debug("t1被啟動(dòng)"); } }; System.out.println(t1.getState()); t1.start(); System.out.println(t1.getState()); }
sleep方法
在sleep期間調(diào)用getState()
方法可以得到TIMED_WAITING
public static void main(String[] args) { Thread t1 = new Thread("線程1") { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t1.start(); log.debug("線程1 state: {}", t1.getState()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("線程1 state: {}", t1.getState()); }
sleep打斷
sleep可以使用interrupt
方法打斷,打斷后會(huì)觸發(fā)InterruptedException
異常
public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread("t1") { @Override public void run() { log.debug("進(jìn)入睡眠"); try { Thread.sleep(2000); } catch (InterruptedException e) { log.debug("被喚醒"); e.printStackTrace(); } } }; t1.start(); Thread.sleep(1000); log.debug("打斷"); t1.interrupt(); }
sleep防止cpu使用100%
在沒(méi)有利用cpu來(lái)計(jì)算時(shí),不要讓while(true)空轉(zhuǎn)浪費(fèi)cpu,這時(shí)可以使用yield或 sleep 來(lái)讓出cpu的使用權(quán)給其他程序
yield方法會(huì)把cpu的使用權(quán)讓出去,然后調(diào)度執(zhí)行其它線程。線程的調(diào)度最終還是依賴的操作系統(tǒng)的調(diào)度器。
join方法
該方法會(huì)等待線程的結(jié)束
static int r = 11; public static void main(String[] args) throws InterruptedException { test1(); } private static void test1() throws InterruptedException { log.debug("主線程開(kāi)始"); Thread t1 = new Thread(() -> { sleep(1); r = 888; },"線程1"); t1.start(); // t1.join(); log.debug(String.valueOf(r)); log.debug("主線程線程結(jié)束"); }
join沒(méi)有使用時(shí),返回的是11,若是使用join返回的是888,是主線程在同步等待線程1。
當(dāng)然還有其他的方法等待
1.sleep方法等待線程1結(jié)束
2.利用FutureTask的get方法
join(long n)方法可以傳入?yún)?shù),等待線程運(yùn)行結(jié)束,最多等待 n 毫秒,假如執(zhí)行時(shí)間大于等待的時(shí)間,就會(huì)不再等待。那么該線程會(huì)直接結(jié)束嗎?答案是不會(huì)。
如下代碼
public class Test10 { static int r = 11; public static void main(String[] args) throws InterruptedException { test1(); } private static void test1() throws InterruptedException { log.debug("主線程開(kāi)始"); Thread t1 = new Thread(() -> { sleep(2); r = 888; log.debug("線程1結(jié)束"); },"線程1"); t1.start(); t1.join(1000); log.debug(String.valueOf(r)); log.debug("主線程線程結(jié)束"); } }
輸出結(jié)果,可以看到這里只是主線程不再等待。
16:28:53.360 c.Test10 [main] - 主線程開(kāi)始
16:28:54.411 c.Test10 [main] - 11
16:28:54.411 c.Test10 [main] - 主線程線程結(jié)束
16:28:55.404 c.Test10 [線程1] - 線程1結(jié)束
interrupt 方法
interrupt可以用來(lái)打斷處于阻塞狀態(tài)的線程。在打斷后,會(huì)有一個(gè)打斷標(biāo)記(布爾值)會(huì)提示是否被打斷過(guò),被打斷過(guò)標(biāo)記為true
否則為false
.
但是sleep、wait和join可以來(lái)清空打斷標(biāo)記。
代碼如下
public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { log.debug("sleep..."); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } },"t1"); t1.start(); Thread.sleep(1000); log.debug("interrupt"); t1.interrupt(); log.debug("打斷標(biāo)記:{}", t1.isInterrupted()); }
線程被打斷后并不會(huì)結(jié)束運(yùn)行,有人就會(huì)問(wèn)了,那我們?nèi)绾卧诖驍嗑€程后關(guān)閉線程呢?答案就是利用打斷標(biāo)記去實(shí)現(xiàn)。
可以在線程的死循環(huán)之中加入一個(gè)判斷去實(shí)現(xiàn)。
boolean interrupted = Thread.currentThread().isInterrupted(); if(interrupted) { log.debug("退出循環(huán)"); break; }
守護(hù)進(jìn)程
Java 進(jìn)程通常需要所有線程都運(yùn)行結(jié)束,才會(huì)結(jié)束。
但是存在一種守護(hù)進(jìn)程,只要其他非守護(hù)進(jìn)程結(jié)束,守護(hù)進(jìn)程就會(huì)結(jié)束。垃圾回收器就使用的守護(hù)進(jìn)程。
線程的狀態(tài)
操作系統(tǒng)層面(早期進(jìn)程的狀態(tài))
- 初始狀態(tài) 在語(yǔ)言層面創(chuàng)建了線程對(duì)象,還未與操作系統(tǒng)線程關(guān)聯(lián)
- 可運(yùn)行狀態(tài)(就緒狀態(tài))指該線程已經(jīng)被創(chuàng)建(與操作系統(tǒng)線程關(guān)聯(lián)),可以由 CPU 調(diào)度執(zhí)行任務(wù)。
- 運(yùn)行狀態(tài) 獲取了 CPU 時(shí)間片運(yùn)行中的狀態(tài)
- 調(diào)用阻塞api使運(yùn)行狀態(tài)轉(zhuǎn)為阻塞狀態(tài)
- 終止?fàn)顟B(tài) 表示線程已經(jīng)執(zhí)行完畢
Java API 層面
1、新建狀態(tài)(New)
Thread t1 = new Thread("t1") { @Override public void run() { log.debug("running..."); } }; log.debug("t1 state {}", t1.getState());
2、就緒狀態(tài)(Runnable)與運(yùn)行狀態(tài)(Running)
Thread t2 = new Thread("t2") { @Override public void run() { while(true) { // runnable } } }; t2.start(); log.debug("t2 state {}", t2.getState());
3、阻塞狀態(tài)(Blocked)
用一個(gè)線程拿到鎖,使得當(dāng)前線程沒(méi)拿到鎖會(huì)出現(xiàn)阻塞狀態(tài)。
Thread t6 = new Thread("t6") { @Override public void run() { synchronized (TestState.class) { // blocked try { Thread.sleep(90000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; t6.start();
4、等待狀態(tài)(Waiting)
等待一個(gè)未執(zhí)行完成的線程
t2.join(); //等待狀態(tài)
5、超時(shí)等待(Time_Waiting)
可以在指定的時(shí)間自行返回的。
6、終止?fàn)顟B(tài)(TERMINATED)
線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。 終止的線程不可再次復(fù)生。
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
spring boot 注入 property的三種方式(推薦)
這篇文章主要介紹了spring boot 注入 property的三種方式,需要的朋友可以參考下2017-07-07SpringCloud Nacos配置中心管理超詳細(xì)講解
這篇文章主要介紹了Springcloud中的Nacos服務(wù)配置,本文以用戶微服務(wù)為例,進(jìn)行統(tǒng)一的配置,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11基于spring+quartz的分布式定時(shí)任務(wù)框架實(shí)現(xiàn)
在Spring中的定時(shí)任務(wù)功能,最好的辦法當(dāng)然是使用Quartz來(lái)實(shí)現(xiàn)。這篇文章主要介紹了基于spring+quartz的分布式定時(shí)任務(wù)框架實(shí)現(xiàn),有興趣的可以了解一下。2017-01-01Java鏈表的天然遞歸結(jié)構(gòu)性質(zhì)圖文與實(shí)例分析
這篇文章主要介紹了Java鏈表的天然遞歸結(jié)構(gòu)性質(zhì),結(jié)合圖文與實(shí)例形式分析了java鏈表中遞歸操作的原理、實(shí)現(xiàn)技巧與相關(guān)注意事項(xiàng),需要的朋友可以參考下2020-03-03詳解SpringBoot啟動(dòng)類的掃描注解的用法及沖突原則
這篇文章主要介紹了詳解SpringBoot啟動(dòng)類的掃描注解的用法及沖突原則,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11