目錄
狀態(tài)?
線程的意義
多線程帶來的風(fēng)險(xiǎn)——線程安全?
線程安全的概念
線程不安全的原因
搶占式執(zhí)行,隨機(jī)性調(diào)度
修改共享數(shù)據(jù)
原子性->加??
可見性
指令重排序
解決線程不安全問題(學(xué)完線程再總結(jié))
synchronized關(guān)鍵字——監(jiān)視器鎖monitor lock?編輯?
?互斥
使用示例
可重入
Java標(biāo)準(zhǔn)庫中線程安全的類
死鎖
1.死鎖是什么
2.死鎖的三個(gè)典型情況
3.死鎖的四個(gè)必要條件?編輯
4.如何破除死鎖?
狀態(tài)是針對當(dāng)前的線程調(diào)度情況進(jìn)行描述的,所以認(rèn)為線程是程序調(diào)度的基本單位(后面再談狀態(tài)都是考慮線程的狀態(tài)了);
狀態(tài)?
public class ThreadState { ? ?public static void main(String[] args) { ? ? ? ?for (Thread.State state : Thread.State.values()) { ? ? ? ? ? ?System.out.println(state); ? ? ? } ? } }
1. NEW : 創(chuàng)建Thread對象,但是還沒調(diào)用start(內(nèi)核還沒創(chuàng)建對應(yīng)的PCB);
2. TERMINATED : 內(nèi)核中的PCB已經(jīng)創(chuàng)建完畢了,但是Thread對象還在;
3. RUNNABLE:可運(yùn)行的;(RUNNABLE是正在CPU上執(zhí)行的,RUNNING是在就緒隊(duì)列上的,可以去CPU上執(zhí)行的);
4.?WAITING
5. TIMED_WAITING
6. BLOCKED
上述三個(gè)都是阻塞,表示線程?PCB 正在阻塞隊(duì)列中,但是原因不同
RUNNABLE表示就緒狀態(tài),包含正在CPU上運(yùn)行,或者在就緒隊(duì)列中排隊(duì) ;
當(dāng)有for循環(huán)里有sleep的時(shí)候,t運(yùn)行中的狀態(tài)大部分是TIMED_WAITING, 為了讓時(shí)間均衡,可以給 t 線程中添加更多的計(jì)算邏輯。
如果線程代碼中全是for循環(huán)里計(jì)算,比較大小之類的操作,此時(shí)這個(gè)線程就不會阻塞,始終是RUNNABLE'狀態(tài);
所以,之前我們學(xué)過的 isAlive() 方法,可以認(rèn)為是處于不是 NEW 和 TERMINATED 的狀態(tài)都是活著 的。
線程的意義
程序分成:
CPU密集:包含大量的加減乘除運(yùn)算符;
IO密集:涉及到讀寫文件,讀寫控制臺,讀寫網(wǎng)絡(luò)的操作;
多線程可以更充分的利用到多核心cpu的資源
多線程帶來的風(fēng)險(xiǎn)——線程安全?
起源于多線程的搶占式執(zhí)行帶來的隨機(jī)性。如果沒有多線程,程序的執(zhí)行順序就是固定的;調(diào)度的源頭來自于操作系統(tǒng)的內(nèi)核實(shí)現(xiàn)
線程安全的概念
多個(gè)線程同時(shí)對某個(gè)共享資源進(jìn)行訪問導(dǎo)致的原子性,可見性,有序性的問題,這些問題導(dǎo)致共享資源存在一個(gè)不可預(yù)測性,使執(zhí)行結(jié)果出現(xiàn)不可預(yù)期的結(jié)果。
如果多線程環(huán)境下代碼運(yùn)行的結(jié)果是符合我們預(yù)期的,即在單線程環(huán)境應(yīng)該的結(jié)果,則說這個(gè)程序是線 程安全的。
線程不安全的原因
搶占式執(zhí)行,隨機(jī)性調(diào)度
修改共享數(shù)據(jù)
多個(gè)線程同時(shí)修改一個(gè)變量
string都是不可變對象,所以他線程安全?
原子性->加??
?是不可拆分的基本單位,針對線程安全問題最主要的的手段就是通過加鎖將非原子的轉(zhuǎn)換為“原子”的,我們把一段代碼想象成一個(gè)房間,每個(gè)線程就是要進(jìn)入這個(gè)房間的人。如果沒有任何機(jī)制保證, A 進(jìn)入 房間之后,還沒有出來;B 是不是也可以進(jìn)入房間,打斷 A 在房間里的隱私。這個(gè)就是不具備原子性 的。那我們應(yīng)該如何解決這個(gè)問題呢?是不是只要給房間加一把鎖, A 進(jìn)去就把門鎖上,其他人是不是就進(jìn) 不來了。這樣就保證了這段代碼的原子性了。有時(shí)也把這個(gè)現(xiàn)象叫做同步互斥,表示操作是互相排斥的。一條 java 語句不一定是原子的,也不一定只是一條指令比如 ?n++ ,其實(shí)是由三步操作組成的:1. 從內(nèi)存把數(shù)據(jù)讀到 CPU2. 進(jìn)行數(shù)據(jù)更新3. 把數(shù)據(jù)寫回到 CPU不保證原子性會給多線程帶來什么問題如果一個(gè)線程正在對一個(gè)變量操作,中途其他線程插入進(jìn)來了,如果這個(gè)操作被打斷了,結(jié)果就可能是 錯(cuò)誤的。這點(diǎn)也和線程的搶占式調(diào)度密切相關(guān) . 如果線程不是 " 搶占 " 的 , 就算沒有原子性 , 也問題不大 .
可見性
?如果一個(gè)線程讀,一個(gè)線程改,也可能出現(xiàn)問題。
指令重排序
編譯器對于指令重排序的前提是 " 保持邏輯不發(fā)生變化 ". 這一點(diǎn)在單線程環(huán)境下比較容易判斷 , 但是在多線程環(huán)境下就沒那么容易了 , 多線程的代碼執(zhí)行復(fù)雜程度更高 , 編譯器很難在編譯階段對代碼的執(zhí)行效果進(jìn)行預(yù)測 , 因此激進(jìn)的重排序很容易導(dǎo)致優(yōu)化后的邏輯和之前不等價(jià) .
解決線程不安全問題(學(xué)完線程再總結(jié))
- 同步代碼塊
- 同步方法
- 鎖
synchronized關(guān)鍵字——監(jiān)視器鎖monitor lock?
?互斥
- 當(dāng)兩個(gè)線程同時(shí)對一個(gè)對象進(jìn)行加鎖的時(shí)候,會出現(xiàn)鎖沖突/鎖競爭,一個(gè)線程可以獲取到鎖另一個(gè)線程出現(xiàn)線程阻塞的情況 ,直到那個(gè)線程解鎖,它才取鎖成功。
- 兩個(gè)線程,一個(gè)加鎖,一個(gè)不加鎖,不存在鎖競爭;
- 兩個(gè)線程針對不同對象加鎖,倆線程獲取到各自的鎖,此時(shí)不存在鎖競爭;
同一個(gè)對象 synchronized 就會 阻塞等待 .進(jìn)入 synchronized 修飾的代碼塊 , 相當(dāng)于 加鎖退出 synchronized 修飾的代碼塊 , 相當(dāng)于 解鎖synchronized 用的鎖是存在 Java 對象頭里的。![]()
?文章來源地址http://www.zghlxwxcb.cn/news/detail-459691.html
?
使用示例
synchronized關(guān)鍵字
修飾方法(普通方法-加到this對象上,靜態(tài)方法-加到類對象上)
代碼塊-手動指定加到某個(gè)對象上
?
ctrl+alt+t->選中代碼塊包裹?
可重入
一個(gè)線程針對同一個(gè)對象,連續(xù)加鎖兩次如果沒有問題,?就叫可重入的,否則不可重入(第二次加鎖就會出現(xiàn)阻塞等待的情況,這種情況線程就僵住了,出現(xiàn)死鎖現(xiàn)象)。
這里的死鎖(死鎖的一種情況)如何理解?
靈異事件:滑稽老鐵去廁所,鎖上了門,結(jié)果時(shí)空錯(cuò)亂,它出來了,忘記了去廁所這件事,去廁所發(fā)現(xiàn)門一直都是鎖的。
為了避免上述死鎖,java將synchronized設(shè)定為可重入的。
?上述圖片可以理解為:
我追到男神,追的過程中給他表白了一次,追到手后再表白了一次,就是“可重入的”,不會出現(xiàn)阻塞等待等。
synchronized 同步塊對同一條線程來說是可重入的,不會出現(xiàn)自己把自己鎖死的問題;
eg:
static class Counter { ? ?public int count = 0; ? ?synchronized void increase() { ? ? ? ?count++; ? } ? ?synchronized void increase2() { ? ? ? ?increase(); ? } }
在可重入鎖的內(nèi)部 , 包含了 " 線程持有者 " 和 " 計(jì)數(shù)器 " 兩個(gè)信息 .1. 如果某個(gè)線程加鎖的時(shí)候 , 發(fā)現(xiàn)鎖已經(jīng)被人占用 , 但是恰好占用的正是自己 , 那么仍然可以繼續(xù)獲取 到鎖, 并讓計(jì)數(shù)器自增 .2. 解鎖的時(shí)候計(jì)數(shù)器遞減為 0 的時(shí)候 , 才真正釋放鎖 . ( 才能被別的線程獲取)
Java標(biāo)準(zhǔn)庫中線程安全的類
多個(gè)線程操作同一個(gè)集合類,就要考慮到線程安全問題。?
?
還有的類不涉及加鎖,但仍然是線程安全的,因?yàn)椴簧婕靶薷牟僮鳎篠tring;?
?
死鎖
1.死鎖是什么
死鎖是兩個(gè)或兩個(gè)以上的線程在執(zhí)行過程中,去爭奪同一個(gè)共享資源導(dǎo)致的互相等待的過程,在沒有外部干預(yù)的情況下,線程會一直處于阻塞狀態(tài),無法向下執(zhí)行。
死鎖比較隱蔽,一旦寫了死鎖,會出現(xiàn)嚴(yán)重的bug
2.死鎖的三個(gè)典型情況
- 一個(gè)線程,一把鎖,連續(xù)鎖兩次,如果鎖是不可重入性鎖,就會死鎖;Java里的synchronized和ReentrantLock都是可重入性鎖;
- 循環(huán)等待:兩個(gè)線程,兩把鎖,t1,t2先各自針對鎖A,鎖B進(jìn)行加鎖,再嘗試獲取對方的鎖。線程1在獲取B的時(shí)候等待線程2釋放B,線程2在獲取A的時(shí)候等待線程1釋放A,造成blocked;(線程1拿到A鎖,再嘗試獲取鎖B,A這把鎖還是繼續(xù)保持的,不會因?yàn)橐カ@取B就把A釋放了)
public class demo33 { public static void main(String[] args) { Object jiangyou=new Object(); Object cu=new Object(); Thread tanglaoshi=new Thread(()->{ synchronized(jiangyou){ try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (cu){ System.out.println("湯老師拿到了醬油和醋"); } } }); Thread shiniang=new Thread(()->{ synchronized(cu){ try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (jiangyou){ System.out.println("師娘拿到了醬油和醋"); } } }); } } //用jconsole定位到這兩個(gè)線程的第二個(gè)??操作出現(xiàn)了BLOCKED操作;
?
- 多個(gè)線程,多把鎖?
當(dāng)他們都拿起左手的筷子就會出現(xiàn)極端情況。 3.死鎖的四個(gè)必要條件
4.如何破除死鎖?
去破壞死鎖中的必要條件中的任意一個(gè)(除了互斥條件),給鎖編號,然后制定一個(gè)固定的順序(從小到大)來加鎖,任意線程加多把鎖的時(shí)候,都讓線程遵循上述循序,此時(shí)循環(huán)等待自然破除。eg:對于上面的代碼就可以通過把鎖的內(nèi)容換一下 。?
還有解決死鎖的銀行家算法。
對于死鎖,我們需要借助jconsole這樣的工具來定位。 在問到死鎖時(shí),記得再解釋下可重入,不可重入
文章來源:http://www.zghlxwxcb.cn/news/detail-459691.html
?
到了這里,關(guān)于線程的狀態(tài),多線程帶來的風(fēng)險(xiǎn),synchronized關(guān)鍵字及死鎖問題的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!