本章開(kāi)始,咸魚君將開(kāi)啟一個(gè)新的專欄 就該這么學(xué)并發(fā)
來(lái)記錄學(xué)習(xí)Java并發(fā)編程的相關(guān)內(nèi)容~
前言
說(shuō)到并發(fā)編程
,就不得不先說(shuō)多線程
,因?yàn)?strong>并發(fā)的本質(zhì)就是多線程的處理
進(jìn)程和線程
- 進(jìn)程 (ps:
每一個(gè)程序就是一個(gè)進(jìn)程
)
進(jìn)程指運(yùn)行中的應(yīng)用程序,每個(gè)程序運(yùn)行時(shí)就會(huì)開(kāi)啟一個(gè)進(jìn)程
進(jìn)程是系統(tǒng)分配資源的最小單位
進(jìn)程有獨(dú)立系統(tǒng)資源(進(jìn)程間的資源是隔離的)
每個(gè)進(jìn)程至少有一個(gè)線程
- 線程 (ps:
進(jìn)程至少有一個(gè)線程
)
線程依托于進(jìn)程
線程除了支持本身運(yùn)行的一點(diǎn)系統(tǒng)資源外(如:
控制線程運(yùn)行的線程控制塊,保留局部變量和少數(shù)參數(shù)的棾茫空間)
不額外擁有獨(dú)立的資源,但是共享進(jìn)程的資源
線程也是CPU調(diào)度的最小單位
我們眼見(jiàn)為實(shí),右鍵打開(kāi)windows系統(tǒng)的任務(wù)管理器
CPU調(diào)度執(zhí)行策略簡(jiǎn)介
CPU如何調(diào)度進(jìn)程/線程呢?
CPU依托于CPU調(diào)度算法
來(lái)調(diào)度,如下
- RR(Round Robin)
時(shí)間片輪轉(zhuǎn)調(diào)度算法,每個(gè)進(jìn)程/線程會(huì)被分配一個(gè)時(shí)間片,
在這個(gè)時(shí)間片的時(shí)間段內(nèi),允許進(jìn)程/線程運(yùn)行
- FIFO((First In First Out)
先進(jìn)先出算法,按照進(jìn)程就緒的先后順序來(lái)使用CPU
- SCBF(Shortest CPU Burst First)
短進(jìn)程優(yōu)先算法,先給每個(gè)進(jìn)程都設(shè)置一個(gè)優(yōu)先級(jí),
根據(jù)比較優(yōu)先級(jí)來(lái)確定下一個(gè)執(zhí)行的進(jìn)程;
將一些相對(duì)短的進(jìn)程優(yōu)先級(jí)適當(dāng)提高
- Virtual RR
虛擬輪轉(zhuǎn)法,時(shí)間片輪轉(zhuǎn)法的改進(jìn)算法
- PSA(Priority Scheduling Algorithm)
優(yōu)先級(jí)算法,給每一個(gè)進(jìn)程一個(gè)優(yōu)先級(jí),
優(yōu)先級(jí)越高的事件先執(zhí)行
- 其它……
看完以上調(diào)度算法, 大家留下個(gè)
CPU有很多調(diào)度算法,并不僅僅只有RR時(shí)間片調(diào)度
這么個(gè)印象就好
因?yàn)楝F(xiàn)在說(shuō)起線程調(diào)度, 很多文章開(kāi)口就是時(shí)間片調(diào)度
,可能會(huì)造成誤解
, 認(rèn)為CPU調(diào)度只有RR算法
一種, 這顯然是不嚴(yán)謹(jǐn)
的.
JVM虛擬機(jī)簡(jiǎn)介
JVM是一個(gè)
虛構(gòu)
出來(lái)的計(jì)算機(jī),是通過(guò)在實(shí)際的計(jì)算機(jī)上仿真模擬
各種計(jì)算機(jī)功能來(lái)實(shí)現(xiàn)的.
引入Java語(yǔ)言虛擬機(jī)后,Java語(yǔ)言在不同平臺(tái)上運(yùn)行時(shí)不需要重新編譯.Java語(yǔ)言使用Java虛擬機(jī)屏蔽了與具體平臺(tái)相關(guān)的信息,使得Java語(yǔ)言只需生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼,就可以在多種平臺(tái)上不加修改地運(yùn)行
簡(jiǎn)而言之
JVM就是個(gè)翻譯器,依托于它,java才實(shí)現(xiàn)了 “一次編譯,多處(平臺(tái))運(yùn)行!”
當(dāng)然,和C,C++等編譯語(yǔ)言不同的點(diǎn)在于,java編譯出來(lái)的是字節(jié)碼文件
,也就是“.class
”文件,這種文件被JVM所認(rèn)識(shí),再由JVM來(lái)翻譯解釋成具體平臺(tái)的可執(zhí)行文件,而后可被具體平臺(tái)運(yùn)行
.
簡(jiǎn)單的流程關(guān)系如下:
java文件(.java)--編譯-->(.class)---->(JVM)解釋---->(可執(zhí)行文件)運(yùn)行
所以
有人說(shuō)JAVA是編譯型語(yǔ)言,也有人說(shuō)是解釋型語(yǔ)言,其實(shí)都沒(méi)什么問(wèn)題!
線程的生命周期
下面我們進(jìn)入本章的正題,著重講解下線程的生命周期
!
先來(lái)張線程生命周期的綱要圖,方便大家理解,后續(xù)針對(duì)每個(gè)節(jié)點(diǎn)細(xì)講.
線程大致有五種狀態(tài)
本章, 我們聊聊線程生命周期的第一個(gè)狀態(tài)“新建”.
新建(New)
當(dāng)線程對(duì)象對(duì)創(chuàng)建后,即進(jìn)入了新建狀態(tài)
- 此時(shí)JVM為其分配內(nèi)存,并初始化其成員變量的值袁余;
- 此時(shí)線程對(duì)象沒(méi)有表現(xiàn)出任何線程的動(dòng)態(tài)特征,程序也不會(huì)執(zhí)行線程的線程執(zhí)行體run()封寞;
線程的創(chuàng)建分為
無(wú)返回值
有返回值
下面我們一一示例
無(wú)返回值的創(chuàng)建方式
- 繼承Thread類
class ExtendsThread extends Thread {
// 重寫run方法
@Override
public void run() {
System.out.println("子線程" + Thread.currentThread().getName() + "正在執(zhí)行 ");
}
}
class Test {
public static void main(String[] args) throws Exception {
ExtendsThread t = new ExtendsThread();
t.start();
}
當(dāng)然,我們也可以簡(jiǎn)單點(diǎn),使用匿名類
的方式創(chuàng)建
class Test {
public static void main(String[] args) throws Exception {
Thread t = new Thread(){
public void run(){
System.out.println("Thread Running");
}
};
t.start();
}
- 實(shí)現(xiàn)Runnable接口
public class ImpRunnableThread implements Runnable {
//重寫run方法
@Override
public void run(){
log.info("子線程{}正在執(zhí)行 ",Thread.currentThread().getName());
}
}
//測(cè)試
class Test {
public void main(String[] args){
ImpRunnableThread t = new ImpRunnableThread();
Thread thread = new Thread(t);
thread.start();
}
}
同樣,我們可以使用匿名類的方式創(chuàng)建
public class Test {
public static void main(String[] args) throws Exception {
Thread t = new Thread(new Runnable(){
@Override
public void run() {
System.out.println("Runnable running");
}
});
t.start();
}
}
有返回值的創(chuàng)建方式
- 使用Callable和Future創(chuàng)建線程
public class ImpCallableThread implements Callable {
//重寫call方法
@Override
public String call() {
log.info("子線程{}正在執(zhí)行 ",Thread.currentThread().getName());
return "返回值";
}
}
//測(cè)試
class Test {
public void main(String[] args){
FutureTask task = new FutureTask(new ImpCallableThread());
new Thread(task).run();
log.info("線程中獲取到的返回信息 {} ",task.get());
}
線程池創(chuàng)建線程
其實(shí)是比較推薦大家用線程池
的方式去創(chuàng)建線程的,好處如下
- 降低資源消耗
通過(guò)重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗
- 提高響應(yīng)速度
當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行
- 提高線程的可管理性
線程是稀缺資源,如果無(wú)限制的創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控
關(guān)于如何創(chuàng)建線程池,其實(shí)Java 已經(jīng)提供了 Executors
,
Java 通過(guò) Executors 提供四種線程池
,例如:
- newCachedThreadPool
創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過(guò)處理需要,
可靈活回收空閑線程,若無(wú)可回收,則新建線程
- newFixedThreadPool
創(chuàng)建一個(gè)定長(zhǎng)線程池,可控制線程最大并發(fā)數(shù),
超出的線程會(huì)在隊(duì)列中等待
- newScheduledThreadPool
創(chuàng)建一個(gè)定長(zhǎng)線程池,支持定時(shí)及周期性任務(wù)執(zhí)行
- newSingleThreadExecutor
創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來(lái)執(zhí)行任務(wù),
保證所有任務(wù)按照指定順序(FIFO,LIFO,優(yōu)先級(jí))執(zhí)行
不過(guò),從實(shí)際開(kāi)發(fā)角度
,直接使用Executors返回線程池會(huì)有一些問(wèn)題,所以我們并不推薦
以上的方式來(lái)創(chuàng)建線程池(阿里內(nèi)部也是明確規(guī)定不允許采用這種方式的
)
原因如下:
FixedThreadPool 和 SingleThreadExecutor : 允許請(qǐng)求的隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE,可能堆積大量的請(qǐng)求,從而導(dǎo)致OOM
CachedThreadPool 和 ScheduledThreadPool : 允許創(chuàng)建的線程數(shù)量為 Integer.MAX_VALUE ,可能會(huì)創(chuàng)建大量線程,從而導(dǎo)致OOM
避免使用Executors創(chuàng)建線程池主要是為了避免其中的默認(rèn)實(shí)現(xiàn),可以改用ThreadPoolExecutor構(gòu)造方法指定參數(shù)
class Test {
public void main(String[] args){
ExecutorService executorService = new ThreadPoolExecutor(5,10,10,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(5));
for (int i = 0; i < 20; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
log.info("線程 {} 開(kāi)始",Thread.currentThread().getName());
}});
}
}
}
需要指定核心線程池的大小
、最大線程池的數(shù)量
节预、保持存活的時(shí)間
叶摄、等待隊(duì)列容量的大小
;
在這種情況下一旦提交的線程數(shù)超過(guò)當(dāng)前可用的線程數(shù)時(shí)就會(huì)拋出拒絕執(zhí)行的異常
java.util.concurrent.RejectedExecutionException
解決方式就是盡量調(diào)大最大線程數(shù)
除了自己定義的ThreadPool
之外,還可以使用開(kāi)源庫(kù) apache guava
等,大家自行選擇如何使用~
哪種創(chuàng)建線程的方式比較好?
關(guān)于這個(gè)問(wèn)題,其實(shí)推薦的是
實(shí)現(xiàn)Runnable接口
因?yàn)?strong>
線程池可以有效的管理實(shí)現(xiàn)了Runnable接口的線程
,如果線程池滿了,新的線程就會(huì)排隊(duì)等候執(zhí)行,直到線程池空閑出來(lái)為止.而如果線程是通過(guò)實(shí)現(xiàn)Thread子類實(shí)現(xiàn)的,這將會(huì)復(fù)雜一些.
有時(shí)我們要同時(shí)融合實(shí)現(xiàn)Runnable接口和Thread子類兩種方式.例如,實(shí)現(xiàn)了Thread子類的實(shí)例可以執(zhí)行多個(gè)實(shí)現(xiàn)了Runnable接口的線程.一個(gè)典型的應(yīng)用就是線程池.
歡迎關(guān)注我
技術(shù)公眾號(hào) “CTO技術(shù)”