知識(shí)點(diǎn)
應(yīng)該了解的概念
1. 線程與進(jìn)程
進(jìn)程是指一個(gè)內(nèi)存中運(yùn)行的應(yīng)用程序伐坏,每個(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間纫塌,一個(gè)進(jìn)程中可以啟動(dòng)多個(gè)線程羽历。比如在 Windows
系統(tǒng)中稽犁,一個(gè)運(yùn)行的 exe
就是一個(gè)進(jìn)程。
線程是指進(jìn)程中的一個(gè)執(zhí)行流程陨帆,一個(gè)進(jìn)程中可以運(yùn)行多個(gè)線程曲秉。比如java.exe
進(jìn)程中可以運(yùn)行很多線程采蚀。線程總是屬于某個(gè)進(jìn)程,進(jìn)程中的多個(gè)線程共享進(jìn)程的內(nèi)存承二。
1.1 區(qū)別
并發(fā)性:進(jìn)程之間可以并發(fā)執(zhí)行搏存,同一個(gè)進(jìn)程的多個(gè)線程之間也可并發(fā)執(zhí)行。
調(diào)度:線程作為調(diào)度和分配的基本單位矢洲,進(jìn)程作為擁有資源的基本單位 璧眠。
根本區(qū)別:進(jìn)程是操作系統(tǒng)資源分配的基本單位,而線程是任務(wù)調(diào)度和執(zhí)行的基本單位
開銷方面:每個(gè)進(jìn)程都有獨(dú)立的代碼和數(shù)據(jù)空間(程序上下文)读虏,進(jìn)程之間切換開銷大责静;線程可以看做輕量級(jí)的進(jìn)程,同一類線程共享代碼和數(shù)據(jù)空間盖桥,每個(gè)線程都有自己獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器(PC)灾螃,線程之間切換的開銷小
包含關(guān)系:線程是進(jìn)程的一部分,所以線程也被稱為輕權(quán)進(jìn)程或者輕量級(jí)進(jìn)程
2. 臨界區(qū)
臨界區(qū)用來表示一種公共資源或者說是共享數(shù)據(jù)揩徊,可以被多個(gè)線程使用腰鬼。但是每個(gè)線程使用時(shí),一旦臨界區(qū)資源被一個(gè)線程占有塑荒,那么其他線程必須等待熄赡。多個(gè)進(jìn)程必須互斥的對(duì)它進(jìn)行訪問。
3. 同步VS異步
同步方法:調(diào)用者必須等待被調(diào)用的方法結(jié)束后齿税,調(diào)用者后面的代碼才能執(zhí)行
異步調(diào)用:調(diào)用者不用管被調(diào)用方法是否完成彼硫,都會(huì)繼續(xù)執(zhí)行后面的代碼,當(dāng)被調(diào)用的方法完成后會(huì)通知調(diào)用者凌箕。
4. 并發(fā)與并行
并發(fā):多個(gè)任務(wù)交替進(jìn)行拧篮,(類似單個(gè) CPU ,通過 CPU 調(diào)度算法等牵舱,處理多個(gè)任務(wù)的能力串绩,叫并發(fā))
并行:真正意義上的“同時(shí)進(jìn)行”。(類似多個(gè) CPU 芜壁,同時(shí)并且處理相同多個(gè)任務(wù)的能力礁凡,叫做并行)
5. 阻塞和非阻塞
阻塞和非阻塞通常用來形容多線程間的相互影響,比如一個(gè)線程占有了臨界區(qū)資源沿盅,那么其他線程需要這個(gè)資源就必須進(jìn)行等待該資源的釋放把篓,會(huì)導(dǎo)致等待的線程掛起纫溃,這種情況就是阻塞腰涧,而非阻塞就恰好相反,它強(qiáng)調(diào)沒有一個(gè)線程可以阻塞其他線程紊浩,所有的線程都會(huì)嘗試地往前運(yùn)行窖铡。
線程與多線程
1. 什么是線程
線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位疗锐,它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位费彼。
2. 為什么需要多線程以及出現(xiàn)的問題
CPU滑臊、內(nèi)存、I/O 設(shè)備的速度是有極大差異的箍铲,為了合理利用 CPU 的高性能雇卷,平衡這三者的速度差異,計(jì)算機(jī)體系結(jié)構(gòu)颠猴、操作系統(tǒng)关划、編譯程序都做出了貢獻(xiàn)
- CPU 增加了緩存,以均衡與內(nèi)存的速度差異翘瓮;// 導(dǎo)致
可見性
問題 - 操作系統(tǒng)增加了進(jìn)程贮折、線程,以分時(shí)復(fù)用 CPU资盅,進(jìn)而均衡 CPU 與 I/O 設(shè)備的速度差異调榄;// 導(dǎo)致
原子性
問題 - 編譯程序優(yōu)化指令執(zhí)行次序,使得緩存能夠得到更加合理地利用呵扛。// 導(dǎo)致
有序性
問題
3. 線程的特征
- main()方法是個(gè)天然的多線程程序 所有的Java 程序每庆,不論并發(fā)與否,都有一個(gè)名為主線程的Thread 對(duì)象今穿。執(zhí)行該程序時(shí)扣孟, Java虛擬機(jī)( JVM )將創(chuàng)建一個(gè)新Thread 并在該線程中執(zhí)行main()方法。這是非并發(fā)應(yīng)用程序中唯一的線程荣赶,也是并發(fā)應(yīng)用程序中的第一個(gè)線程凤价。
- Java中的線程共享應(yīng)用程序中的所有資源,包括內(nèi)存和打開的文件拔创,快速而簡(jiǎn)單地共享信息利诺。但是必須使用同步避免數(shù)據(jù)競(jìng)爭(zhēng)
- Java中的所有線程都有一個(gè)優(yōu)先級(jí),這個(gè)整數(shù)值介于Thread.MIN_PRIORITY(1)和Thread.MAX_PRIORITY(10)之間剩燥,默認(rèn)優(yōu)先級(jí)是Thread.NORM_PRIORITY(5)慢逾。線程的執(zhí)行順序并沒有保證,通常灭红,較高優(yōu)先級(jí)的線程將在較低優(yōu)先級(jí)的線程之前執(zhí)行侣滩。
4. 線程狀態(tài)
- 創(chuàng)建(NEW):新創(chuàng)建了一個(gè)線程對(duì)象,但還沒有調(diào)用 start() 方法变擒。
- 運(yùn)行(RUNNABLE):Java 線程中將就緒(ready)和運(yùn)行中(running)兩種狀態(tài)籠統(tǒng)的稱為“運(yùn)行”君珠。
線程對(duì)象創(chuàng)建后,其他線程(比如 main 線程)調(diào)用了該對(duì)象的 start() 方法娇斑。該狀態(tài)的線程位于可運(yùn)行線程池中策添,等待被線程調(diào)度選中材部,獲取 CPU 的使用權(quán),此時(shí)處于就緒狀態(tài)(ready)唯竹。就緒狀態(tài)的線程在獲得 CPU 時(shí)間片后變?yōu)檫\(yùn)行中狀態(tài)(running)乐导。
- 阻塞(BLOCKED):表示線程阻塞于鎖。線程的執(zhí)行過程中由于一些原因進(jìn)入阻塞狀態(tài)比如:調(diào)用 sleep 方法浸颓、嘗試去得到一個(gè)鎖等等
- 超時(shí)等待(TIMED_WAITING):該狀態(tài)不同于WAITING物臂,它可以在指定的時(shí)間后自行返回。
- 等待(WAITING):進(jìn)入該狀態(tài)的線程需要等待其他線程做出一些特定動(dòng)作(通知或中斷)产上。
- 終止(TERMINATED):表示該線程已經(jīng)執(zhí)行完畢鹦聪。
- 運(yùn)行(running):CPU 開始調(diào)度線程,并開始執(zhí)行 run 方法
- 消亡(dead):run 方法執(zhí)行完 或者 執(zhí)行過程中遇到了一個(gè)異常
5. 使用多線程一定快嗎蒂秘?
答:不一定泽本,因?yàn)槎嗑€程會(huì)進(jìn)行上下文切換,上下文切換會(huì)帶來開銷姻僧。
5.1 什么是上下文切換规丽?
單核在一個(gè)時(shí)刻只能運(yùn)行一個(gè)線程,當(dāng)在運(yùn)行一個(gè)線程的過程中轉(zhuǎn)去運(yùn)行另外一個(gè)線程撇贺,這個(gè)叫做線程上下文切換(對(duì)于進(jìn)程也是類似)赌莺。
線程上下文切換過程中會(huì)記錄 程序計(jì)數(shù)器、CPU 寄存器 的 狀態(tài)等數(shù)據(jù)松嘶。
5.2 如何減少上下文切換艘狭?
5.2.1 減少線程的數(shù)量
由于一個(gè)CPU每個(gè)時(shí)刻只能執(zhí)行一條線程,而傲嬌的我們又想讓程序并發(fā)執(zhí)行翠订,操作系統(tǒng)只好不斷地進(jìn)行上下文切換來使我們從感官上覺得程序是并發(fā)執(zhí)的行巢音。因此,我們只要減少線程的數(shù)量尽超,就能減少上下文切換的次數(shù)官撼。
5.2.2 控制同一把鎖上的線程數(shù)量
多條線程共用同一把鎖,那么當(dāng)一條線程獲得鎖后似谁,其他線程就會(huì)被阻塞傲绣;當(dāng)該線程釋放鎖后,操作系統(tǒng)會(huì)從被阻塞的線程中選一條執(zhí)行巩踏,從而又會(huì)出現(xiàn)上下文切換
因此秃诵,減少同一把鎖上的線程數(shù)量也能減少上下文切換的次數(shù)
5.2.3 采用無鎖并發(fā)編程
- 需要并發(fā)執(zhí)行的任務(wù)是無狀態(tài)的:HASH分段
所謂無狀態(tài)是指并發(fā)執(zhí)行的任務(wù)沒有共享變量,他們都獨(dú)立執(zhí)行塞琼。對(duì)于這種類型的任務(wù)可以按照ID進(jìn)行HASH分段菠净,每段用一條線程去執(zhí)行。
- 需要并發(fā)執(zhí)行的任務(wù)是有狀態(tài)的:CAS算法
如果任務(wù)需要修改共享變量,那么必須要控制線程的執(zhí)行順序嗤练,否則會(huì)出現(xiàn)安全性問題。你可以給任務(wù)加鎖在讶,保證任務(wù)的原子性與可見性煞抬,但這會(huì)引起阻塞,從而發(fā)生上下文切換构哺;為了避免上下文切換革答,你可以使用CAS算法, 僅在線程內(nèi)部需要更新共享變量時(shí)使用CAS算法來更新曙强,這種方式不會(huì)阻塞線程残拐,并保證更新過程的安全性(具體的方式后面的文章會(huì)將)。
6. 使用多線程的缺點(diǎn)
6.1 上下文切換的開銷
當(dāng) CPU 從執(zhí)行一個(gè)線程切換到執(zhí)行另外一個(gè)線程的時(shí)候碟嘴,它需要先存儲(chǔ)當(dāng)前線程的本地的數(shù)據(jù)溪食,程序指針等,然后載入另一個(gè)線程的本地?cái)?shù)據(jù)娜扇,程序指針等错沃,最后才開始執(zhí)行。這種切換稱為“上下文切換”雀瓢。CPU 會(huì)在一個(gè)上下文中執(zhí)行一個(gè)線程枢析,然后切換到另外一個(gè)上下文中執(zhí)行另外一個(gè)線程。上下文切換并不廉價(jià)刃麸。如果沒有必要醒叁,應(yīng)該減少上下文切換的發(fā)生。
6.2 增加資源消耗
線程在運(yùn)行的時(shí)候需要從計(jì)算機(jī)里面得到一些資源泊业。 除了 CPU把沼,線程還需要一些 內(nèi)存來維持它本地的堆棧。它也需要 占用操作系統(tǒng)中一些資源來管理線程
6.3 編程更復(fù)雜
在多線程訪問共享數(shù)據(jù)的時(shí)候吁伺,要考慮 **線程安全 **問題
7. 線程狀態(tài)的基本操作
線程在生命周期內(nèi)還有需要基本操作智政,而這些操作會(huì)成為線程間一種通信方式,比如使用中斷(interrupted)方式通知實(shí)現(xiàn)線程間的交互等等
7.1 interrupted
中斷可以理解為線程的一個(gè)標(biāo)志位箱蝠,它表示了一個(gè)運(yùn)行中的線程是否被其他線程進(jìn)行了中斷操作续捂。中斷好比其他線程對(duì)該線程打了一個(gè)招呼。其他線程可以調(diào)用該線程的interrupt()方法對(duì)其進(jìn)行中斷操作宦搬,同時(shí)該線程可以調(diào)用 isInterrupted()來感知其他線程對(duì)其自身的中斷操作牙瓢,從而做出響應(yīng)。另外间校,同樣可以調(diào)用Thread的靜態(tài)方法 interrupted()對(duì)當(dāng)前線程進(jìn)行中斷操作矾克,該方法會(huì)清除中斷標(biāo)志位。需要注意的是憔足,當(dāng)拋出InterruptedException時(shí)候胁附,會(huì)清除中斷標(biāo)志位酒繁,也就是說在調(diào)用isInterrupted會(huì)返回false。
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
//sleepThread睡眠1000ms
final Thread sleepThread = new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
super.run();
}
};
//busyThread一直執(zhí)行死循環(huán)
Thread busyThread = new Thread() {
@Override
public void run() {
while (true) ;
}
};
sleepThread.start();
busyThread.start();
sleepThread.interrupt();
busyThread.interrupt();
while (sleepThread.isInterrupted()) ;
System.out.println("sleepThread isInterrupted: " + sleepThread.isInterrupted());
System.out.println("busyThread isInterrupted: " + busyThread.isInterrupted());
}
}
開啟了兩個(gè)線程分別為sleepThread和BusyThread, sleepThread睡眠1s控妻,BusyThread執(zhí)行死循環(huán)州袒。然后分別對(duì)著兩個(gè)線程進(jìn)行中斷操作,可以看出sleepThread拋出InterruptedException后清除標(biāo)志位弓候,而busyThread就不會(huì)清除標(biāo)志位郎哭。
另外,同樣可以通過中斷的方式實(shí)現(xiàn)線程間的簡(jiǎn)單交互菇存, while (sleepThread.isInterrupted()) 表示在Main中會(huì)持續(xù)監(jiān)測(cè)sleepThread夸研,一旦sleepThread的中斷標(biāo)志位清零,即sleepThread.isInterrupted()返回為false時(shí)才會(huì)繼續(xù)Main線程才會(huì)繼續(xù)往下執(zhí)行依鸥。因此亥至,中斷操作可以看做線程間一種簡(jiǎn)便的交互方式。一般在結(jié)束線程時(shí)通過中斷標(biāo)志位或者標(biāo)志位的方式可以有機(jī)會(huì)去清理資源贱迟,相對(duì)于武斷而直接的結(jié)束線程抬闯,這種方式要優(yōu)雅和安全。
7.2 join
join方法可以看做是線程間協(xié)作的一種方式关筒,很多時(shí)候溶握,一個(gè)線程的輸入可能非常依賴于另一個(gè)線程的輸出,這就像兩個(gè)好基友蒸播,一個(gè)基友先走在前面突然看見另一個(gè)基友落在后面了睡榆,這個(gè)時(shí)候他就會(huì)在原處等一等這個(gè)基友,等基友趕上來后袍榆,就兩人攜手并進(jìn)胀屿。其實(shí)線程間的這種協(xié)作方式也符合現(xiàn)實(shí)生活。在軟件開發(fā)的過程中包雀,從客戶那里獲取需求后宿崭,需要經(jīng)過需求分析師進(jìn)行需求分解后,這個(gè)時(shí)候產(chǎn)品才写,開發(fā)才會(huì)繼續(xù)跟進(jìn)葡兑。
join方法源碼關(guān)鍵是:
while (isAlive()) {
wait(0);
}
線程的合并是指將某一個(gè)線程A在調(diào)用A.join()方法合并到正在運(yùn)行的另一個(gè)線程B中,此時(shí)線程B處于阻塞狀態(tài)需要等到線程A執(zhí)行完畢后才開始線程B的繼續(xù)執(zhí)行
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
TestThread t = new TestThread();
Thread t1 = new Thread(t);
t1.start();
for (int i = 0; i < 100; i++) {
/**
* 當(dāng)main線程中的i等于50的時(shí)候赞草,就把t1線程合并到main線程中執(zhí)行讹堤。此時(shí)main線程是處于阻塞狀態(tài)
* 直到t1線程執(zhí)行完成后,main才開始繼續(xù)執(zhí)行
*/
if (50==i) {
t1.join();
}
System.out.println("main.."+i);
}
}
}
class TestThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("join.."+i);
}
}
}
7.3 sleep
public static native void sleep(long millis)方法顯然是Thread的靜態(tài)方法厨疙,很顯然它是讓當(dāng)前線程按照指定的時(shí)間休眠洲守,其休眠時(shí)間的精度取決于處理器的計(jì)時(shí)器和調(diào)度器。需要注意的是如果當(dāng)前線程獲得了鎖,sleep方法并不會(huì)失去鎖梗醇。
public static void main(String[] args) throws InterruptedException {
int num = 10;
while (true) {
System.out.println(num--);
Thread.sleep(1000);
if (num<=0) {
break;
}
}
}
7.4 yield
該暫停方法暫停的時(shí)候不一定就暫停了知允,取決于CPU,假如剛暫停CPU調(diào)度又調(diào)到了該線程那就又啟動(dòng)了.....
public class YieldDemo {
public static void main(String[] args) throws InterruptedException {
TestThread1 t = new TestThread1();
Thread t1 = new Thread(t);
t1.start();
for (int i = 0; i < 100; i++) {
//當(dāng)main線程中的i是20的倍數(shù)時(shí)叙谨,就暫停main線程
if (i%20==0) {
Thread.yield();//yield寫在哪個(gè)線程體中温鸽,就暫停哪個(gè)線程。這里是在main里唉俗,就暫停main線程
System.out.println("main線程暫停");
}
System.out.println("main.."+i);
}
}
}
class TestThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("join.."+i);
}
}
}
8. 兩類線程
8.1 用戶線程(User Thread)
User和Daemon兩者幾乎沒有區(qū)別嗤朴,唯一的不同之處就在于虛擬機(jī)的離開:如果 User Thread已經(jīng)全部退出運(yùn)行了配椭,只剩下Daemon Thread存在了虫溜,虛擬機(jī)也就退出了。
8.2 守護(hù)線程(Daemon Thread)
Daemon的作用是為其他線程的運(yùn)行提供便利服務(wù)股缸,守護(hù)線程最典型的應(yīng)用就是 GC (垃圾回收器)
當(dāng)所有非守護(hù)線程結(jié)束時(shí)衡楞,程序也就終止,同時(shí)會(huì)殺死所有守護(hù)線程敦姻。main() 屬于非守護(hù)線程瘾境。
使用 setDaemon() 方法將一個(gè)線程設(shè)置為守護(hù)線程。
8.2.1 需要注意
- thread.setDaemon(true)必須在thread.start()之前設(shè)置镰惦,否則會(huì)拋出一個(gè)IllegalThreadStateException異常迷守。你不能把正在運(yùn)行的常規(guī)線程設(shè)置為守護(hù)線程。
- 在Daemon線程中產(chǎn)生的新線程也是Daemon的
- 不要認(rèn)為所有的應(yīng)用都可以分配給Daemon來進(jìn)行服務(wù)旺入,比如讀寫操作或者計(jì)算邏輯兑凿。
8.2.2 守護(hù)線程的代碼實(shí)踐
- 前臺(tái)線程是保證執(zhí)行完畢的,后臺(tái)線程還沒有執(zhí)行完畢就退出了茵瘾。
public class Test {
public static void main(String args) {
Thread t1 = new MyCommon();
Thread t2 = new Thread(new MyDaemon());
t2.setDaemon(true); //設(shè)置為守護(hù)線程
t2.start();
t1.start();
}
}
class MyCommon extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("線程1第" + i + "次執(zhí)行礼华!");
try {
Thread.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyDaemon implements Runnable {
public void run() {
for (long i = 0; i < 9999999L; i++) {
System.out.println("后臺(tái)線程第" + i + "次執(zhí)行!");
try {
Thread.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
執(zhí)行結(jié)果:
后臺(tái)線程第0次執(zhí)行拗秘!
線程1第0次執(zhí)行圣絮!
線程1第1次執(zhí)行!
后臺(tái)線程第1次執(zhí)行雕旨!
后臺(tái)線程第2次執(zhí)行扮匠!
線程1第2次執(zhí)行!
線程1第3次執(zhí)行凡涩!
后臺(tái)線程第3次執(zhí)行餐禁!
線程1第4次執(zhí)行!
后臺(tái)線程第4次執(zhí)行突照!
后臺(tái)線程第5次執(zhí)行帮非!
后臺(tái)線程第6次執(zhí)行!
后臺(tái)線程第7次執(zhí)行!
- 守護(hù)線程在退出的時(shí)候并不會(huì)執(zhí)行finnaly塊中的代碼末盔,所以將釋放資源等操作不要放在finnaly塊中執(zhí)行筑舅,這種操作是不安全的
public class DaemonDemo {
public static void main(String[] args) {
Thread daemonThread = new Thread(new Runnable() {
@Override
public void run() {
//daemodThread run方法中是一個(gè)while死循環(huán)
while (true) {
try {
System.out.println("i am alive");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("finally block");
}
}
}
});
daemonThread.setDaemon(true);
daemonThread.start();
//確保main線程結(jié)束前能給daemonThread能夠分到時(shí)間片
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
執(zhí)行結(jié)果
i am alive
finally block
i am alive
daemodThread run方法中是一個(gè)while死循環(huán),會(huì)一直打印,但是當(dāng)main線程結(jié)束后daemonThread就會(huì)退出所以不會(huì)出現(xiàn)死循環(huán)的情況陨舱。
main線程先睡眠800ms保證daemonThread能夠擁有一次時(shí)間片的機(jī)會(huì)翠拣,也就是說可以正常執(zhí)行一次打印“i am alive”操作和一次finally塊中"finally block"操作。
緊接著main 線程結(jié)束后游盲,daemonThread退出误墓,這個(gè)時(shí)候只打印了"i am alive"并沒有打印finnal塊中的。
守護(hù)線程在退出的時(shí)候并不會(huì)執(zhí)行finnaly塊中的代碼益缎,所以將釋放資源等操作不要放在finnaly塊中執(zhí)行谜慌,這種操作是不安全的
8.2.3 特點(diǎn)
因?yàn)槭鞘刈o(hù)線程,或者說是支持性線程莺奔,就
意味著這個(gè)線程并不屬于程序中不可或缺的一部分
欣范。所以當(dāng)所有的非守護(hù)線程(即用戶線程)結(jié)束之后,程序就會(huì)結(jié)束令哟,JVM退出恼琼,同時(shí)也就會(huì)殺死所有的非守護(hù)線程。所以也就意味著屏富,守護(hù)線程不適合去訪問固有資源晴竞,比如文件节吮,數(shù)據(jù)庫
计维。因?yàn)殡S時(shí)可能中斷涩笤。后臺(tái)線程會(huì)隨著主程序的結(jié)束而結(jié)束远舅,但是前臺(tái)進(jìn)程則不會(huì)本今,或者說只要有一個(gè)前臺(tái)線程未退出肌访,進(jìn)程就不會(huì)終止女气。
默認(rèn)情況下肮之,程序員創(chuàng)建的線程是用戶線程瘤袖;用setDaemon(true)可以設(shè)置線程為后臺(tái)線程衣摩;而用isDaemon( )則可以判斷一個(gè)線程是前臺(tái)線程還是后臺(tái)線程;
jvm的垃圾回收器其實(shí)就是一個(gè)后臺(tái)線程捂敌;
setDaemon函數(shù)必須在start函數(shù)之前設(shè)定艾扮,否則會(huì)拋出IllegalThreadStateException異常
;
8.2.4 使用場(chǎng)景
qq,飛訊等等聊天軟件,主程序是非守護(hù)線程,而所有的聊天窗口是守護(hù)線程 ,當(dāng)在聊天的過程中,直接關(guān)閉聊天應(yīng)用程序時(shí),聊天窗口也會(huì)隨之關(guān)閉,但是不是 立即關(guān)閉,而是需要緩沖,等待接收到關(guān)閉命令后才會(huì)執(zhí)行窗口關(guān)閉操作.
jvm中,gc線程是守護(hù)線程,作用就是當(dāng)所有用戶自定義線以及主線程執(zhí)行完畢后, gc線程才停止
Web服務(wù)器中的Servlet,在容器啟動(dòng)時(shí)占婉,后臺(tái)都會(huì)初始化一個(gè)服務(wù)線程泡嘴,即調(diào)度線程,負(fù)責(zé)處理http請(qǐng)求逆济,然后每個(gè)請(qǐng)求過來酌予,調(diào)度線程就會(huì)從線程池中取出一個(gè)工作者線程來處理該請(qǐng)求磺箕,從而實(shí)現(xiàn)并發(fā)控制的目的。也就是說抛虫,一個(gè)實(shí)際應(yīng)用在Java的線程池中的調(diào)度線程
8.2.5 總結(jié)
守護(hù)線程就是用來告訴JVM松靡,我的這個(gè)線程是一個(gè)低級(jí)別的線程,不需要等待它運(yùn)行完才退出建椰,讓JVM喜歡什么時(shí)候退出就退出雕欺,不用管這個(gè)線程。
在日常的業(yè)務(wù)相關(guān)的CRUD開發(fā)中棉姐,其實(shí)并不會(huì)關(guān)注到守護(hù)線程這個(gè)概念屠列,也幾乎不會(huì)用上。
但是如果要往更高的地方走的話伞矩,這些深層次的概念還是要了解一下的笛洛,比如一些框架的底層實(shí)現(xiàn)。
7. 線程安全
7.1 什么是線程安全扭吁?
所有的隱患都是在多個(gè)線程訪問的情況下產(chǎn)生的撞蜂,也就是我們要確保在多條線程訪問的時(shí)候盲镶,我們的程序還能按照我們預(yù)期的行為去執(zhí)行.
下面代碼需要在多線程環(huán)境下的測(cè)試
Integer count = 0;
public void getCount() {
count ++;
System.out.println(count);
}
//開三個(gè)線程代碼侥袜,值會(huì)重復(fù) 所以在多線程的情況下
結(jié)論:
當(dāng)多個(gè)線程訪問某個(gè)方法時(shí),不管你通過怎樣的調(diào)用方式溉贿、或者說這些線程如何交替地執(zhí)行枫吧,我們?cè)谥鞒绦蛑胁恍枰プ鋈魏蔚耐剑@個(gè)類的結(jié)果行為都是我們?cè)O(shè)想的正確行為宇色,那么我們就可以說這個(gè)類是線程安全的九杂。
7.2 什么時(shí)候會(huì)出現(xiàn)線程安全?
線程安全問題都是由全局變量及靜態(tài)變量引起的。若每個(gè)線程中對(duì)全局變量宣蠕、靜態(tài)變量只有讀操作例隆,而無寫操作,一般來說抢蚀,這個(gè)全局變量是線程安全的镀层;
若有多個(gè)線程同時(shí)執(zhí)行寫操作,一般都需要考慮線程同步皿曲,否則的話就可能出現(xiàn)線程安全的問題唱逢。
7.3 有哪些方法可以保證線程安全嗎?
7.3.1 synchronized
7.3.1.1 同步代碼塊
synchronized 關(guān)鍵字可以用于方法中的某個(gè)區(qū)塊中,表示只對(duì)這個(gè)區(qū)塊的資源實(shí)行互斥訪問屋休。
synchronized關(guān)鍵字就是用來控制線程同步的坞古,保證我們的線程在多線程環(huán)境下,不被多個(gè)線程同時(shí)執(zhí)行劫樟,確保我們數(shù)據(jù)的完整性痪枫,使用方法一般是加在方法上织堂。
代碼塊中的鎖對(duì)象可以是任意對(duì)象;
但是必須保證多個(gè)線程使用的鎖對(duì)象是同一個(gè)奶陈;
鎖對(duì)象的作用就是將同步代碼塊鎖住捧挺,只允許一個(gè)線程在同步代碼塊中執(zhí)行
// 創(chuàng)建一個(gè)鎖對(duì)象
Object object = new Object();
int count = 0; // 記錄方法的命中次數(shù)
// 創(chuàng)建同步代碼塊
synchronized (object) {
if (ticket > 0) {
count++ ;
int i = 1;
j = j + i;
}
}
注意:
- synchronized鎖的是括號(hào)里的對(duì)象,而不是代碼尿瞭,其次闽烙,對(duì)于非靜態(tài)的synchronized方法,鎖的是對(duì)象本身也就是this
- synchronized鎖住一個(gè)對(duì)象之后声搁,別的線程如果想要獲取鎖對(duì)象黑竞,那么就必須等這個(gè)線程執(zhí)行完釋放鎖對(duì)象之后才可以,否則一直處于等待狀態(tài)疏旨。
- 加synchronized關(guān)鍵字很魂,可以讓我們的線程變得安全,但是我們?cè)谟玫臅r(shí)候檐涝,也要注意縮小synchronized的使用范圍遏匆,如果隨意使用時(shí)很影響程序的性能,別的對(duì)象想拿到鎖谁榜,結(jié)果你沒用鎖還一直把鎖占用幅聘,這樣就有點(diǎn)浪費(fèi)資源。
7.3.1.2 同步方法
使用 synchronized 修飾的方法,就叫做同步方法,保證A線程執(zhí)行該方法的時(shí)候,其他線程只能在方法外等著
int count = 0; // 記錄方法的命中次數(shù)
public synchronized void threadMethod(int j) {
count++ ;
int i = 1;
j = j + i;
}
7.3.2 同步鎖(Lock)
java.util.concurrent.locks.Lock 機(jī)制提供了比 synchronized 代碼塊和 synchronized 方法更廣泛的鎖定操作窃植,同步代碼塊/同步方法具有的功能 Lock 都有,除此之外更強(qiáng)大,更體現(xiàn)面向?qū)ο蟆?/p>
Lock 鎖使用步驟:
- 在成員位置創(chuàng)建一個(gè) ReentrantLock 對(duì)象帝蒿;
- 在可能出現(xiàn)安全問題的代碼前調(diào)用 Lock 接口中的方法lock();
- 在可能出現(xiàn)安全問題的代碼前調(diào)用 Lock 接口中的方法unLock();
7.3.2.1 lock.lock()
跟synchronized不同的是,Lock獲取的所對(duì)象需要我們親自去進(jìn)行釋放
巷怜,為了防止我們代碼出現(xiàn)異常葛超,所以我們的釋放鎖操作放在finally中
,因?yàn)閒inally中的代碼無論如何都是會(huì)執(zhí)行的延塑。
private Lock lock = new ReentrantLock(); // ReentrantLock是Lock的子類
private void method(Thread thread){
lock.lock(); // 獲取鎖對(duì)象
try {
System.out.println("線程名:"+thread.getName() + "獲得了鎖");
// Thread.sleep(2000);
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("線程名:"+thread.getName() + "釋放了鎖");
lock.unlock(); // 釋放鎖對(duì)象
}
}
7.3.2.2 lock.tryLock()
tryLock()這個(gè)方法跟Lock()是有區(qū)別的绣张,Lock在獲取鎖的時(shí)候,如果拿不到鎖关带,就一直處于等待狀態(tài)侥涵,直到拿到鎖,但是tryLock()卻不是這樣的豫缨,tryLock是有一個(gè)Boolean的返回值的独令,如果沒有拿到鎖,直接返回false好芭,停止等待燃箭,它不會(huì)像Lock()那樣去一直等待獲取鎖。
private void method(Thread thread){
// lock.lock(); // 獲取鎖對(duì)象
if (lock.tryLock()) {
try {
System.out.println("線程名:"+thread.getName() + "獲得了鎖");
// Thread.sleep(2000);
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("線程名:"+thread.getName() + "釋放了鎖");
lock.unlock(); // 釋放鎖對(duì)象
}
}
}
7.3.2.3 lock.tryLock(5,TimeUnit.SECONDS)
一種方式來控制一下舍败,讓后面等待的線程招狸,可以等待5秒敬拓,如果5秒之后,還獲取不到鎖裙戏,那么就停止等乘凸,其實(shí)tryLock()是可以進(jìn)行設(shè)置等待的相應(yīng)時(shí)間的。
private void method(Thread thread) throws InterruptedException {
// lock.lock(); // 獲取鎖對(duì)象
// 如果5秒內(nèi)獲取不到鎖對(duì)象累榜,那就不再等待
if (lock.tryLock(5,TimeUnit.SECONDS)) {
try {
System.out.println("線程名:"+thread.getName() + "獲得了鎖");
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("線程名:"+thread.getName() + "釋放了鎖");
lock.unlock(); // 釋放鎖對(duì)象
}
}
}