java 技術(shù)學(xué)習(xí)文檔

1. HashMap原理

jdk8后采用數(shù)組+鏈表+紅黑樹的數(shù)據(jù)結(jié)構(gòu),利用元素的key的hash值對(duì)數(shù)組長(zhǎng)度取模得到在數(shù)組上的位置雾袱。當(dāng)出現(xiàn)hash值一樣的情形旬渠,就在數(shù)組上的對(duì)應(yīng)位置形成一條鏈表迫吐。據(jù)碰撞越來越多大于8的時(shí)候,就會(huì)把鏈表轉(zhuǎn)換成紅黑樹讯检。

2. HashMap中put()如何實(shí)現(xiàn)的

https://blog.csdn.net/qq_38182963/article/details/78942764

1.Key.hashCode和無符號(hào)右移16位做異或運(yùn)算得到hash值,取模運(yùn)算計(jì)算下標(biāo)index

對(duì)key的hashCode 和右移16位做異或運(yùn)算,之后hash(key) & (capacity - 1)做按位與運(yùn)算得到下標(biāo)姆涩。

2.下標(biāo)的位置沒有元素說明沒有發(fā)生碰撞挽拂,直接添加元素到散列表中去

3.如果發(fā)生了碰撞(hash值相同),進(jìn)行三種判斷

4.1:若key地址相同或equals相同骨饿,則替換舊值

4.2:key不相等亏栈,如果是紅黑樹結(jié)構(gòu)台腥,就調(diào)用樹的插入方法

4.3:key不相等,也不是紅黑樹绒北,循環(huán)遍歷直到鏈表中某個(gè)節(jié)點(diǎn)為空黎侈,用尾插法(1.8)/頭插法(1.7)創(chuàng)建新結(jié)點(diǎn)插入到鏈表中,遍歷到有節(jié)點(diǎn)哈希值相同則覆蓋闷游,如果峻汉,鏈表的長(zhǎng)度大于等于8了,則將鏈表改為紅黑樹脐往。

4.如果桶滿了大于閥值休吠,則resize進(jìn)行擴(kuò)容

3. HashMap中g(shù)et()如何實(shí)現(xiàn)的

1.Key.hashCode的高16位做異或運(yùn)算得到hash值,取模運(yùn)算計(jì)算下標(biāo)index

2.找到所在的鏈表的頭結(jié)點(diǎn),遍歷鏈表,如果key值相等业簿,返回對(duì)應(yīng)的value值,否則返回null

4.為什么HashMap線程不安全

1.多線程put的時(shí)候可能導(dǎo)致元素丟失

2.put非null元素后get出來的卻是null

3.多線程擴(kuò)容,引起的死循環(huán)問題

1.put的時(shí)候會(huì)根據(jù)tab[index]是否為空?qǐng)?zhí)行直接插入還是走鏈表紅黑樹邏輯, 并發(fā)時(shí),如果兩個(gè)put 的key發(fā)生了碰撞,同時(shí)執(zhí)行判斷tab[index]是否為空,兩個(gè)都是空,會(huì)同時(shí)插入,就會(huì)導(dǎo)致其中一個(gè)線程的 put 的數(shù)據(jù)被覆蓋瘤礁。

2.元素個(gè)數(shù)超出threshold擴(kuò)容會(huì)創(chuàng)建一個(gè)新hash表,最后將舊hash表中元素rehash到新的hash表中梅尤, 將舊數(shù)組中的元素置null柜思。線程1執(zhí)行put時(shí),線程2執(zhí)行去訪問原table,get為null克饶。

3.在擴(kuò)容的時(shí)候可能會(huì)讓鏈表形成環(huán)路酝蜒。原因是會(huì)重新計(jì)算元素在新的table里面桶的位置,而且還會(huì)將鏈表翻轉(zhuǎn)過來矾湃。 多線程并發(fā)resize擴(kuò)容,頭插造成了逆序A-B 變成了C-B ,t1執(zhí)行e為A亡脑,next為B掛起, t2執(zhí)行完畢導(dǎo)致B指向A邀跃,繼續(xù)執(zhí)行t1霉咨,他繼續(xù)先頭插e(cuò)A,再頭插nextB拍屑, 由于t2程導(dǎo)致B后面有A途戒,所以繼續(xù)頭插, A插到B前面,出現(xiàn)環(huán)狀鏈表僵驰。 get一個(gè)在這個(gè)鏈表中不存在的key時(shí)喷斋,就會(huì)出現(xiàn)死循環(huán)了。

https://juejin.im/post/6844903796225605640#heading-5

https://coolshell.cn/articles/9606.html/comment-page-3#comments

https://www.iteye.com/blog/firezhfox-2241043

5.HashMap1.7和1.8有哪些區(qū)別

參考: https://blog.csdn.net/qq_36520235/article/details/82417949

由數(shù)組+鏈表的結(jié)構(gòu)改為數(shù)組+鏈表+紅黑樹蒜茴。

優(yōu)化了高位運(yùn)算的hash算法:h^(h>>>16)

擴(kuò)容后星爪,元素要么是在原位置,要么是在原位置再移動(dòng)2次冪的位置粉私,且鏈表順序不變顽腾。

頭插改為尾插

(1)由 數(shù)組+鏈表 的結(jié)構(gòu)改為 數(shù)組+鏈表+紅黑樹 。

拉鏈過長(zhǎng)會(huì)嚴(yán)重影響hashmap的性能诺核, 在鏈表元素?cái)?shù)量超過8時(shí)改為紅黑樹抄肖,少于6時(shí)改為鏈表久信,中間7不改是避免頻繁轉(zhuǎn)換降低性能。

(2) 優(yōu)化了高位運(yùn)算的hash算法

h^(h>>>16)將hashcode無符號(hào)右移16位漓摩,讓高16位和低16位進(jìn)行異或裙士。

(3)擴(kuò)容 擴(kuò)容后數(shù)據(jù)存儲(chǔ)位置的計(jì)算方式也不一樣

1.7是直接用hash值和需要擴(kuò)容的二進(jìn)制數(shù)進(jìn)行與操作,1.8(n-1)&hash,位運(yùn)算省去了重新計(jì)算hash幌甘,只需要判斷hash值新增的位是0還是1潮售,0的話索引沒變,1的話索引變?yōu)樵饕釉瓉淼臄?shù)組長(zhǎng)度 锅风,且鏈表順序不變酥诽。

(4)JDK1.7用的是頭插法,而JDK1.8及之后使用的都是尾插法皱埠。

因?yàn)镴DK1.7是用單鏈表進(jìn)行的縱向延伸肮帐,當(dāng)采用頭插法時(shí)會(huì)容易出現(xiàn)逆序鏈表形成環(huán)路導(dǎo)致死循環(huán)問題。 但是在JDK1.8之后是因?yàn)榧尤肓思t黑樹,使用尾插法边器,能夠避免出現(xiàn)逆序且鏈表死循環(huán)的問題训枢。

6.解決hash沖突的時(shí)候,為什么用紅黑樹

鏈表取元素是從頭結(jié)點(diǎn)一直遍歷到對(duì)應(yīng)的結(jié)點(diǎn)忘巧,這個(gè)過程的復(fù)雜度是O(N) 恒界, 而紅黑樹基于二叉樹的結(jié)構(gòu),查找元素的復(fù)雜度為O(logN) 砚嘴, 所以十酣,當(dāng)元素個(gè)數(shù)過多時(shí),用紅黑樹存儲(chǔ)可以提高搜索的效率际长。

7.紅黑樹的效率高耸采,為什么一開始就用紅黑樹存儲(chǔ)呢?

紅黑樹雖然查詢效率比鏈表高工育,但是結(jié)點(diǎn)占用的空間大虾宇,treenodes的大小大約是常規(guī)節(jié)點(diǎn)的兩倍 只有達(dá)到一定的數(shù)目才有樹化的意義,這是基于時(shí)間和空間的平衡考慮如绸。 如果一開始就用紅黑樹結(jié)構(gòu)嘱朽,元素太少,新增效率又比較慢怔接,無疑這是浪費(fèi)性能的燥翅。

8.不用紅黑樹,用二叉查找樹可以不

https://blog.csdn.net/T_yoo_csdn/article/details/87163439

但是二叉查找樹在特殊情況下會(huì)變成一條線性結(jié)構(gòu)

如果構(gòu)建根節(jié)點(diǎn)以后插入的數(shù)據(jù)是有序的蜕提,那么構(gòu)造出來的二叉搜索樹就不是平衡樹,而是一個(gè)鏈表靶端,它的時(shí)間復(fù)雜度就是 O(n)谎势,遍歷查找會(huì)非常慢凛膏。

紅黑樹,每次更新數(shù)據(jù)以后再進(jìn)行平衡脏榆,以此來保證其查找效率猖毫。

9.為什么閥值是8才轉(zhuǎn)為紅黑樹

容器中節(jié)點(diǎn)分布在hash桶中的頻率遵循泊松分布

各個(gè)長(zhǎng)度的命中概率依次遞減,源碼注釋中給我們展示了1-8長(zhǎng)度的具體命中概率须喂。

當(dāng)長(zhǎng)度為8的時(shí)候吁断,概率概率僅為0.00000006,這么小的概率坞生,大于上千萬個(gè)數(shù)據(jù)時(shí)HashMap的紅黑樹轉(zhuǎn)換幾乎不會(huì)發(fā)生仔役。

10.為什么退化為鏈表的閾值是6

主要是一個(gè)過渡,避免鏈表和紅黑樹之間頻繁的轉(zhuǎn)換是己。 如果一個(gè)HashMap不停的插入又兵、刪除元素,鏈表個(gè)數(shù)在8左右徘徊卒废, 就會(huì)頻繁的發(fā)生樹轉(zhuǎn)鏈表沛厨、鏈表轉(zhuǎn)樹,效率會(huì)很低摔认。

11.hash沖突你還知道哪些解決辦法逆皮?

(1)開放定址法 (2)鏈地址法 (3)再哈希法 (4)公共溢出區(qū)域法

12.HashMap在什么條件下擴(kuò)容

如果bucket滿了(超過load factor*current capacity),就要resize参袱。

為什么負(fù)載因子是0.75 小于0.5电谣,空著一半就擴(kuò)容了, 如果是0.5 蓖柔, 那么每次達(dá)到容量的一半就進(jìn)行擴(kuò)容辰企,默認(rèn)容量是16, 達(dá)到8就擴(kuò)容成32况鸣,達(dá)到16就擴(kuò)容成64牢贸, 最終使用空間和未使用空間的差值會(huì)逐漸增加,空間利用率低下镐捧。

當(dāng)負(fù)載因子是1.0的時(shí)候潜索, 出現(xiàn)大量的Hash的沖突時(shí),底層的紅黑樹變得異常復(fù)雜懂酱。對(duì)于查詢效率極其不利竹习。這種情況就是犧牲了時(shí)間來保證空間的利用率。

是0.75的時(shí)候

空間利用率比較高列牺,而且避免了相當(dāng)多的Hash沖突整陌,使得底層的鏈表或者是紅黑樹的高度比較低,提升了空間效率。

13.HashMap中hash函數(shù)怎么實(shí)現(xiàn)的泌辫?還有哪些hash函數(shù)的實(shí)現(xiàn)方式随夸?

對(duì)key的hashCode 和右移16位做異或運(yùn)算,之后hash(key) & (capacity - 1)做按位與運(yùn)算得到下標(biāo)。

Hash函數(shù)是指把一個(gè)大范圍映射到一個(gè)小范圍震放。把大范圍映射到一個(gè)小范圍的目的往往是為了節(jié)省空間宾毒,使得數(shù)據(jù)容易保存。

如果不同的輸入得到了同一個(gè)哈希值殿遂,就發(fā)生了"哈希碰撞"(collision)诈铛。

比較出名的有MurmurHash、MD4墨礁、MD5等等幢竹。

14.為什么不直接將hashcode作為哈希值去做取模,而是要先高16位異或低16位?

均勻散列表的下標(biāo),降低hash沖突的幾率。 不融合高低位,hashcode返回的值都是高位的變動(dòng)的話,造成散列的值都是同一個(gè)饵溅。 融合后妨退,高位的數(shù)據(jù)會(huì)影響到 index 的變換,依然可以保持散列的隨機(jī)性。 打個(gè)比方,當(dāng)我們的length為16的時(shí)候刽射,哈希碼(字符串“abcabcabcabcabc”的key對(duì)應(yīng)的哈希碼)對(duì)(16-1)與操作,對(duì)于多個(gè)key生成的hashCode幸乒,只要哈希碼的后4位為0,不論不論高位怎么變化唇牧,最終的結(jié)果均為0罕扎。 擾動(dòng)函數(shù)優(yōu)化后:減少了碰撞的幾率。

15.為什么擴(kuò)容是2的次冪?

%運(yùn)算不如位移運(yùn)算快

在 B 是 2 的冪情況下:A % B = A & (B - 1)

和這個(gè)(n - 1) & hash的計(jì)算方法有著千絲萬縷的關(guān)系 按位與&的計(jì)算方法是丐重,只有當(dāng)對(duì)應(yīng)位置的數(shù)據(jù)都為1時(shí)腔召,運(yùn)算結(jié)果也為1,當(dāng)HashMap的容量是2的n次冪時(shí)扮惦,(n-1)的2進(jìn)制也就是1111111***111這樣形式的臀蛛,這樣與添加元素的hash值進(jìn)行位運(yùn)算時(shí),能夠充分的散列崖蜜,使得添加的元素均勻分布在HashMap的每個(gè)位置上浊仆,減少hash碰撞。

例如長(zhǎng)度為8時(shí)候豫领,3&(8-1)=3 2&(8-1)=2 抡柿,不同位置上,不碰撞等恐。

而長(zhǎng)度為5的時(shí)候洲劣,3&(5-1)=0 2&(5-1)=0备蚓,都在0上,出現(xiàn)碰撞了

16.鏈表的查找的時(shí)間復(fù)雜度是多少?

HashMap 如果完全不存在沖突則 通過 key 獲取 value 的時(shí)間復(fù)雜度就是 O(1)闪檬, 如果出現(xiàn)哈希碰撞星著,HashMap 里面每一個(gè)數(shù)組(桶)里面存的其實(shí)是一個(gè)鏈表,這時(shí)候再通過 key 獲取 value 的時(shí)候時(shí)間復(fù)雜度就變成了 O(n)粗悯, HashMap 當(dāng)一個(gè) key 碰撞次數(shù)超過8 的時(shí)候就會(huì)把鏈表轉(zhuǎn)換成紅黑樹,使得查詢的時(shí)間復(fù)雜度變成了O(logN)同欠。 通過高16位異或低16位運(yùn)算降低hash沖突幾率样傍。

17.紅黑樹
1.什么是虛擬機(jī)

Java 虛擬機(jī)是一個(gè)字節(jié)碼翻譯器,它將字節(jié)碼文件翻譯成各個(gè)系統(tǒng)對(duì)應(yīng)的機(jī)器碼铺遂,確保字節(jié)碼文件能在各個(gè)系統(tǒng)正確運(yùn)行衫哥。 Java 虛擬機(jī)規(guī)范去讀取 Class 文件,并按照規(guī)定去解析襟锐、執(zhí)行字節(jié)碼指令撤逢。

2.Jvm的內(nèi)存模型

[圖片上傳失敗...(image-50e2e-1631067660635)]

https://www.cnblogs.com/chanshuyi/p/jvm_serial_06_jvm_memory_model.html

https://www.cnblogs.com/yychuyu/p/13275970.html

https://juejin.cn/post/6844903636829487112#heading-22

線程都共享的部分:Java 堆、方法區(qū)粮坞、常量池

線程的私有數(shù)據(jù):PC寄存器蚊荣、Java 虛擬機(jī)棧、本地方法棧

Java 堆

Java 堆指的是從 JVM 劃分出來的一塊區(qū)域莫杈,這塊區(qū)域?qū)iT用于 Java 實(shí)例對(duì)象的內(nèi)存分配互例,幾乎所有實(shí)例對(duì)象都在會(huì)這里進(jìn)行內(nèi)存的分配

Java 堆根據(jù)對(duì)象存活時(shí)間的不同,Java 堆還被分為年輕代筝闹、老年代兩個(gè)區(qū)域媳叨,年輕代還被進(jìn)一步劃分為 Eden 區(qū)、From Survivor 0关顷、To Survivor 1 區(qū)

[圖片上傳中...(image-b05856-1631067660635-5)]

當(dāng)有對(duì)象需要分配時(shí)糊秆,一個(gè)對(duì)象永遠(yuǎn)優(yōu)先被分配在年輕代的 Eden 區(qū),等到 Eden 區(qū)域內(nèi)存不夠時(shí)议双,Java 虛擬機(jī)會(huì)啟動(dòng)垃圾回收痘番。此時(shí) Eden 區(qū)中沒有被引用的對(duì)象的內(nèi)存就會(huì)被回收,而一些存活時(shí)間較長(zhǎng)的對(duì)象則會(huì)進(jìn)入到老年代

什么 Java 堆要進(jìn)行這樣一個(gè)區(qū)域劃分

虛擬機(jī)中的對(duì)象必然有存活時(shí)間長(zhǎng)的對(duì)象聋伦,也有存活時(shí)間短的對(duì)象夫偶,這是一個(gè)普遍存在的正態(tài)分布規(guī)律。如果因?yàn)榇婊顣r(shí)間短的對(duì)象有很多觉增,那么勢(shì)必導(dǎo)致較為頻繁的垃圾回收兵拢。而垃圾回收時(shí)不得不對(duì)所有內(nèi)存都進(jìn)行掃描,但其實(shí)有一部分對(duì)象逾礁,它們存活時(shí)間很長(zhǎng)说铃,對(duì)他們進(jìn)行掃描完全是浪費(fèi)時(shí)間访惜。因此為了提高垃圾回收效率

Java虛擬機(jī)棧

Java 虛擬機(jī)棧,線程私有腻扇,生命周期和線程一致

每一個(gè)運(yùn)行時(shí)的線程债热,都有一個(gè)獨(dú)立的棧。棧中記錄了方法調(diào)用的歷史幼苛,每有一次方法調(diào)用窒篱,棧中便會(huì)多一個(gè)棧楨

每個(gè)方法在執(zhí)行時(shí)都會(huì)床創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲(chǔ)局部變量表、操作數(shù)棧舶沿、動(dòng)態(tài)鏈接墙杯、方法出口等信息。每一個(gè)方法從調(diào)用直至執(zhí)行結(jié)束括荡,就對(duì)應(yīng)著一個(gè)棧幀從虛擬機(jī)棧中入棧到出棧的過程高镐。

撕開棧幀,一不小心畸冲,局部變量表嫉髓、操作數(shù)棧、動(dòng)態(tài)鏈接邑闲、方法出口 嘩啦啦地散落一地算行。

棧楨中通常包含四個(gè)信息:

局部變量:方法參數(shù)和方法中定義的局部變量,對(duì)象引用

操作數(shù)棧:存放的就是方法當(dāng)中的各種操作數(shù)的臨時(shí)空間

動(dòng)態(tài)連接:Class文件的常量池中存在有大量的符號(hào)引用,而將部分符號(hào)引用在運(yùn)行期間轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化即為動(dòng)態(tài)鏈接

返回地址:當(dāng)前方法的返回地址监憎,一個(gè)方法在執(zhí)行完畢之后纱意,就應(yīng)該返回到方法外面之后繼續(xù)執(zhí)行main()后面的代碼(應(yīng)該返回到下一條指令執(zhí)行位置)。

本地方法棧

與java虛擬機(jī)棧類似鲸阔,不過存放的是native方法執(zhí)行時(shí)的局部變量等數(shù)據(jù)存放位置偷霉。因?yàn)閚ative方法一般不是由java語(yǔ)言編寫的,常見的就是.dll文件當(dāng)中的方法(由C/C++編寫)褐筛,比如Thread類中start()方法在運(yùn)行時(shí)就會(huì)調(diào)用到一個(gè)start0()方法类少,查看源碼時(shí)就會(huì)看到private native void start0();這個(gè)方法就是一個(gè)本地方法。本地方法的作用就相當(dāng)于是一個(gè)“接口”渔扎,用來連接java和其他語(yǔ)言的接口硫狞。

方法區(qū)

[圖片上傳失敗...(image-f4ad6b-1631067660635)]

方法區(qū)中,存儲(chǔ)了每個(gè)

1.類的信息

類的名稱

類的訪問描述符(public晃痴、private残吩、default、abstract倘核、final泣侮、static)

2.字段信息(該類聲明的所有字段)

字段修飾符(public、protect紧唱、private活尊、default)

字段的類型

字段名稱

3.方法信息

方法修飾符

方法返回類型

方法名

4.類變量(靜態(tài)變量)

就是靜態(tài)字段( public static String static_str="static_str";)

虛擬機(jī)在使用某個(gè)類之前隶校,必須在方法區(qū)為這些類變量分配空間。 5.指向類加載器的引用

6.指向Class實(shí)例的引用

7.運(yùn)行時(shí)常量池(Runtime Constant Pool)

永久代和方法區(qū)的關(guān)系

《Java虛擬機(jī)規(guī)范》只是規(guī)定了有方法區(qū)這么個(gè)概念和它的作用蛹锰,并沒有規(guī)定如何去實(shí)現(xiàn)它深胳。那么,在不同的 JVM 上方法區(qū)的實(shí)現(xiàn)肯定是不同的了铜犬。 同時(shí)大多數(shù)用的JVM都是Sun公司的HotSpot舞终。在HotSpot上把GC分代收集擴(kuò)展至方法區(qū),或者說使用永久代來實(shí)現(xiàn)方法區(qū)癣猾。因此权埠,我們得到了結(jié)論,永久代是HotSpot的概念煎谍,方法區(qū)是Java虛擬機(jī)規(guī)范中的定義,是一種規(guī)范龙屉,而永久代是一種實(shí)現(xiàn)呐粘,一個(gè)是標(biāo)準(zhǔn)一個(gè)是實(shí)現(xiàn)。其他的虛擬機(jī)實(shí)現(xiàn)并沒有永久帶這一說法转捕。Java7及以前版本的Hotspot中方法區(qū)位于永久代中作岖,HotSpot 使用永久代實(shí)現(xiàn)方法區(qū),HotSpot 使用 GC分代來實(shí)現(xiàn)方法區(qū)內(nèi)存回收五芝。

元空間

Java8痘儡, HotSpots取消了永久代,那么是不是也就沒有方法區(qū)了呢枢步?當(dāng)然不是沉删,方法區(qū)是一個(gè)規(guī)范,規(guī)范沒變醉途,它就一直在矾瑰。那么取代永久代的就是元空間。它可永久代有什么不同的隘擎?

存儲(chǔ)位置不同殴穴,永久代物理是是堆的一部分,和新生代货葬,老年代地址是連續(xù)的采幌,而元空間屬于本地內(nèi)存;

存儲(chǔ)內(nèi)容不同震桶,元空間存儲(chǔ)類的元信息休傍,靜態(tài)變量和常量池等并入堆中。相當(dāng)于永久代的數(shù)據(jù)被分到了堆和元空間中尼夺。

Java8為什么要將永久代替換成Metaspace尊残?

字符串存在永久代中炒瘸,容易出現(xiàn)性能問題和內(nèi)存溢出。

類及方法的信息等比較難確定其大小寝衫,因此對(duì)于永久代的大小指定比較困 難顷扩,太小容易出現(xiàn)永久代溢出,太大則容易導(dǎo)致老年代溢出慰毅。

永久代會(huì)為 GC 帶來不必要的復(fù)雜度隘截,并且回收效率偏低。

常量池

分為class常量池和運(yùn)行時(shí)常量池汹胃,運(yùn)行時(shí)的常量池是屬于方法區(qū)的一部分婶芭,而Class常量池是Class文件中的。

Class常量池

[圖片上傳失敗...(image-328ea5-1631067660635)]

class 文件中除了包含類的版本着饥、字段犀农、方法、接口等描述信息外宰掉,還有一項(xiàng)信息就是常量池 呵哨,用于存放編譯器生成的各種字面量 和符號(hào)引用 。

String str = "str"; int i = 1; "str"和1都是字面量轨奄,有別于變量孟害。

符號(hào)引用:可以是任意類型的字面量。只要能無歧義的定位到目標(biāo)挪拟。在編譯期間由于暫時(shí)不知道類的直接引用挨务,因此先使用符號(hào)引用代替。最終還是會(huì)轉(zhuǎn)換為直接引用訪問目標(biāo)玉组。

運(yùn)行時(shí)常量池

行時(shí)常量池相對(duì)于 Class 文件常量池來說具備動(dòng)態(tài)性谎柄,Class 文件常量只是一個(gè)靜態(tài)存儲(chǔ)結(jié)構(gòu),里面的引用都是符號(hào)引用球切。而運(yùn)行時(shí)常量池可以在運(yùn)行期間將符號(hào)引用解析為直接引用

字符串常量池

運(yùn)行時(shí)常量池中的字符串字面量若是成員的谷誓,則在類的加載初始化階段就使用到了字符串常量池;若是本地的吨凑,則在使用到的時(shí)候(執(zhí)行此代碼時(shí))才會(huì)使用到字符串常量池

在 jdk1.6(含)之前也是方法區(qū)的一部分捍歪,并且其中存放的是字符串的實(shí)例;

在 jdk1.7(含)之后是在堆內(nèi)存之中鸵钝,存儲(chǔ)的是字符串對(duì)象的引用糙臼,字符串實(shí)例是在堆中;

jdk1.8 已移除永久代恩商,字符串常量池是在本地內(nèi)存當(dāng)中变逃,存儲(chǔ)的也只是引用。

程序計(jì)數(shù)器

每個(gè)線程啟動(dòng)的時(shí)候怠堪,都會(huì)創(chuàng)建一個(gè)PC(Program Counter揽乱,程序計(jì)數(shù)器)寄存器名眉,是保存線程當(dāng)前正在執(zhí)行的方法。如果這個(gè)方法不是 native 方法凰棉,那么 PC 寄存器就保存 Java 虛擬機(jī)正在執(zhí)行的字節(jié)碼指令地址损拢。如果是 native 方法,那么 PC 寄存器保存的值是 undefined

3.類加載機(jī)制

https://www.cnblogs.com/chanshuyi/p/jvm_serial_07_jvm_class_loader_mechanism.html

https://zhuanlan.zhihu.com/p/33509426

http://www.ityouknow.com/jvm/2017/08/19/class-loading-principle.html

https://juejin.im/post/6876968255597051917#heading-12

Java 虛擬機(jī)把源碼編譯為字節(jié)碼之后撒犀,虛擬機(jī)便可以將字節(jié)碼讀取進(jìn)內(nèi)存福压,從而進(jìn)行解析、運(yùn)行等整個(gè)過程或舞,這個(gè)過程叫:Java 虛擬機(jī)的類加載機(jī)制荆姆。

JVM 虛擬機(jī)執(zhí)行 class 字節(jié)碼的過程可以分為七個(gè)階段:加載、驗(yàn)證映凳、準(zhǔn)備胆筒、解析、初始化诈豌、使用腐泻、卸載。

在這五個(gè)階段中队询,加載、驗(yàn)證构诚、準(zhǔn)備和初始化這四個(gè)階段發(fā)生的順序是確定的蚌斩,而解析階段則不一定,它在某些情況下可以在初始化階段之后開始范嘱,這是為了支持Java語(yǔ)言的運(yùn)行時(shí)綁定送膳。

另外注意這里的幾個(gè)階段是按順序開始,而不是按順序進(jìn)行或完成丑蛤,因?yàn)檫@些階段通常都是互相交叉地混合進(jìn)行的叠聋,通常在一個(gè)階段執(zhí)行的過程中調(diào)用或激活另一個(gè)階段。

[圖片上傳中...(image-7ca411-1631067660635-2)]

加載

簡(jiǎn)單來說受裹,加載指的是把class字節(jié)碼文件從各個(gè)來源通過類加載器裝載入內(nèi)存中碌补。

  • 通過一個(gè)類的全限定名來獲取其定義的二進(jìn)制字節(jié)流。
  • 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)棉饶。
  • 在Java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象厦章,作為對(duì)方法區(qū)中這些數(shù)據(jù)的訪問入口。

驗(yàn)證

主要是為了保證加載進(jìn)來的字節(jié)流符合虛擬機(jī)規(guī)范照藻,不會(huì)造成安全錯(cuò)誤袜啃。

  • 文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范;例如:是否以0xCAFEBABE開頭幸缕、主次版本號(hào)是否在當(dāng)前虛擬機(jī)的處理范圍之內(nèi)
  • 元數(shù)據(jù)驗(yàn)證:對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析,類中的字段群发,方法是否與父類沖突晰韵?是否出現(xiàn)了不合理的重載?
  • 字節(jié)碼驗(yàn)證:保證程序語(yǔ)義的合理性熟妓,比如要保證類型轉(zhuǎn)換的合理性雪猪。
  • 符號(hào)引用驗(yàn)證:校驗(yàn)符號(hào)引用中的訪問性(private,public等)是否可被當(dāng)前類訪問滑蚯?

準(zhǔn)備

主要是為類變量(注意浪蹂,不是實(shí)例變量)分配內(nèi)存,并且賦予初值

1.Java語(yǔ)言支持的變量類型有:

類變量:獨(dú)立于方法之外的變量告材,用 static 修飾坤次。

實(shí)例變量:獨(dú)立于方法之外的變量,不過沒有 static 修飾斥赋。

局部變量:類的方法中的變量缰猴。

在準(zhǔn)備階段,JVM 只會(huì)為「類變量」分配內(nèi)存疤剑,而不會(huì)為「類成員變量」分配內(nèi)存滑绒。「類成員變量」的內(nèi)存分配需要等到初始化階段才開始隘膘。

例如下面的代碼在準(zhǔn)備階段疑故,只會(huì)為 factor 屬性分配內(nèi)存,而不會(huì)為 website 屬性分配內(nèi)存弯菊。

public static int factor = 3;

public String website = "www.cnblogs.com/chanshuyi";

2.初始化的類型纵势。在準(zhǔn)備階段,JVM 會(huì)為類變量分配內(nèi)存管钳,并為其初始化钦铁。但是這里的初始化指的是為變量賦予 Java 語(yǔ)言中該數(shù)據(jù)類型的零值,而不是用戶代碼里初始化的值才漆。

例如下面的代碼在準(zhǔn)備階段之后牛曹,sector 的值將是 0,而不是 3醇滥。

public static int sector = 3;

解析

將常量池內(nèi)的符號(hào)引用替換為直接引用的過程黎比。

在解析階段,虛擬機(jī)會(huì)把所有的類名鸳玩,方法名焰手,字段名這些符號(hào)引用替換為具體的內(nèi)存地址或偏移量,也就是直接引用怀喉。

舉個(gè)例子來說书妻,現(xiàn)在調(diào)用方法hello(),這個(gè)方法的地址是1234567,那么hello就是符號(hào)引用躲履,1234567就是直接引用见间。

初始化

這個(gè)階段主要是對(duì)類變量初始化,是執(zhí)行類構(gòu)造器的過程工猜。

換句話說米诉,只對(duì)static修飾的變量或語(yǔ)句進(jìn)行初始化。

類初始化時(shí)機(jī): 有當(dāng)對(duì)類的主動(dòng)使用的時(shí)候會(huì)先進(jìn)行類的初始化篷帅,類的主動(dòng)使用包括以下4種:

1.創(chuàng)建類的實(shí)例史侣,調(diào)用類的靜態(tài)方法,訪問某個(gè)類或接口的靜態(tài)變量如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化

2.使用 java.lang.reflect 包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候魏身,如果類沒有進(jìn)行過初始化惊橱,則需要先觸發(fā)其初始化。

3.當(dāng)虛擬機(jī)啟動(dòng)時(shí)箭昵,用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類)税朴,虛擬機(jī)會(huì)先初始化這個(gè)主類。

4.當(dāng)初始化一個(gè)類的時(shí)候家制,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化正林,則需要先觸發(fā)其父類的初始化

如果同時(shí)包含多個(gè)靜態(tài)變量和靜態(tài)代碼塊,則按照自上而下的順序依次執(zhí)行颤殴。

java對(duì)象實(shí)例化時(shí)的順序?yàn)椋焊割悆?yōu)于子類觅廓,靜態(tài)優(yōu)于非靜態(tài),只有在第一次創(chuàng)建對(duì)象的時(shí)候才會(huì)初始化靜態(tài)塊涵但。

1哪亿,父類的靜態(tài)成員變量和靜態(tài)代碼塊加載

2,子類的靜態(tài)成員變量和靜態(tài)代碼塊加載

3贤笆,父類成員變量和方法塊加載

4,父類的構(gòu)造函數(shù)加載

5讨阻,子類成員變量和方法塊加載

6芥永,子類的構(gòu)造函數(shù)加載

4.類加載器

在 JVM 中有三個(gè)非常重要的編譯器,它們分別是:前端編譯器钝吮、JIT 編譯器埋涧、AOT 編譯器。

前端編譯器奇瘦,最常見的就是我們的 javac 編譯器棘催,其將 Java 源代碼編譯為 Java 字節(jié)碼文件。JIT 即時(shí)編譯器耳标,其將 Java 字節(jié)碼編譯為本地機(jī)器代碼醇坝。AOT 編譯器則能將源代碼直接編譯為本地機(jī)器碼。

ClassLoader 代表類加載器次坡,是 java 的核心組件呼猪,可以說所有的 class 文件都是由類加載器從外部讀入系統(tǒng)画畅,然后交由 jvm 進(jìn)行后續(xù)的連接、初始化等操作宋距。

jvm 會(huì)創(chuàng)建三種類加載器轴踱,分別為啟動(dòng)類加載器、擴(kuò)展類加載器和應(yīng)用類加載器

啟動(dòng)類加載器

主要負(fù)責(zé)加載系統(tǒng)的核心類谚赎,負(fù)責(zé)加載存放在JDK\jre\lib(JDK代表JDK的安裝目錄淫僻,下同)下

擴(kuò)展類加載器

主要用于加載 lib\ext 中的 java 類,或者由java.ext.dirs系統(tǒng)變量指定的路徑中的所有類庫(kù)(如javax.開頭的類

應(yīng)用類加載器

Application ClassLoader 主要加載用戶類壶唤,即加載用戶類路徑(ClassPath)上指定的類庫(kù)雳灵,一般都是我們自己寫的代碼

類加載有三種方式:

1、命令行啟動(dòng)應(yīng)用時(shí)候由JVM初始化加載

2视粮、通過Class.forName()方法動(dòng)態(tài)加載

3细办、通過ClassLoader.loadClass()方法動(dòng)態(tài)加載

Class.forName()和ClassLoader.loadClass()區(qū)別

Class.forName():將類的.class文件加載到j(luò)vm中之外,還會(huì)對(duì)類進(jìn)行解釋蕾殴,執(zhí)行類中的static塊笑撞;

ClassLoader.loadClass():只干一件事情,就是將.class文件加載到j(luò)vm中钓觉,不會(huì)執(zhí)行static中的內(nèi)容,只有在newInstance才會(huì)去執(zhí)行static塊茴肥。

Class.forName(name, initialize, loader)帶參函數(shù)也可控制是否加載static塊。并且只有調(diào)用了newInstance()方法采用調(diào)用構(gòu)造函數(shù)荡灾,創(chuàng)建類的對(duì)象 瓤狐。

Class.forName()方法實(shí)際上也是調(diào)用的CLassLoader來實(shí)現(xiàn)的。

雙親委派模型

雙親委派模型的工作流程是:如果一個(gè)類加載器收到了類加載的請(qǐng)求批幌,它首先不會(huì)自己去嘗試加載這個(gè)類础锐,而是把請(qǐng)求委托給父加載器去完成,依次向上荧缘,因此皆警,所有的類加載請(qǐng)求最終都應(yīng)該被傳遞到頂層的啟動(dòng)類加載器中含滴,只有當(dāng)父加載器在它的搜索范圍中沒有找到所需的類時(shí)遭商,即無法完成該加載酷师,子加載器才會(huì)嘗試自己去加載該類础废。

[圖片上傳失敗...(image-98a345-1631067660635)]

雙親委派模式優(yōu)勢(shì)

避免重復(fù)加載 + 避免核心類篡改

采用雙親委派模式的是好處是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系占卧,通過這種層級(jí)關(guān)可以避免類的重復(fù)加載愉老,當(dāng)父親已經(jīng)加載了該類時(shí)驶兜,就沒有必要子ClassLoader再加載一次吞鸭。其次是考慮到安全因素珊蟀,java核心api中定義類型不會(huì)被隨意替換菊值,假設(shè)通過網(wǎng)絡(luò)傳遞一個(gè)名為java.lang.Integer的類,通過雙親委托模式傳遞到啟動(dòng)類加載器,而啟動(dòng)類加載器在核心Java API發(fā)現(xiàn)這個(gè)名字的類俊性,發(fā)現(xiàn)該類已被加載略步,并不會(huì)重新加載網(wǎng)絡(luò)傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class定页,這樣便可以防止核心API庫(kù)被隨意篡改趟薄。

5.垃圾回收機(jī)制

如何判斷一個(gè)對(duì)象是死亡的

如果一個(gè)對(duì)象不可能再被引用,那么這個(gè)對(duì)象就是垃圾典徊,應(yīng)該被回收

https://www.zhihu.com/question/21539353

引用計(jì)數(shù)法

在一個(gè)對(duì)象被引用時(shí)加一杭煎,被去除引用時(shí)減一,對(duì)于計(jì)數(shù)器為0的對(duì)象意味著是垃圾對(duì)象卒落,可以被GC回收羡铲。

優(yōu)點(diǎn):

引用計(jì)數(shù)收集器執(zhí)行簡(jiǎn)單,判定效率高儡毕,交織在程序運(yùn)行中也切。對(duì)程序不被長(zhǎng)時(shí)間打斷的實(shí)時(shí)環(huán)境比較有利。

缺點(diǎn):

難以檢測(cè)出對(duì)象之間的循環(huán)引用腰湾。 引用計(jì)數(shù)器增加了程序執(zhí)行的開銷雷恃。

可達(dá)性算法

從 GC Root 出發(fā),所有可達(dá)的對(duì)象都是存活的對(duì)象费坊,而所有不可達(dá)的對(duì)象都是垃圾倒槐。, 當(dāng)一個(gè)對(duì)象到 GC Roots 沒有任何引用鏈相連時(shí), 即該對(duì)象不可達(dá)附井。

可以作為GC Roots的對(duì)象

虛擬機(jī)棧的局部變量引用的對(duì)象讨越;

本地方法棧的JNI所引用的對(duì)象;

方法區(qū)的靜態(tài)變量和常量所引用的對(duì)象永毅;

https://blog.csdn.net/u010798968/article/details/72835255

[圖片上傳失敗...(image-20fac9-1631067660635)]

對(duì)象實(shí)例1把跨、2、4沼死、6都具有GC Roots可達(dá)性着逐,也就是存活對(duì)象,不能被GC回收的對(duì)象漫雕。 而對(duì)于對(duì)象實(shí)例3、5直接雖然連通峰鄙,但并沒有任何一個(gè)GC Roots與之相連浸间,這便是GC Roots不可達(dá)的對(duì)象,這就是GC需要回收的垃圾對(duì)象吟榴。

垃圾回收算法

標(biāo)記清除算法

對(duì)根集合進(jìn)行掃描魁蒜,對(duì)存活的對(duì)象進(jìn)行標(biāo)記。標(biāo)記完成后,再對(duì)整個(gè)空間內(nèi)未被標(biāo)記的對(duì)象掃描兜看,進(jìn)行回收锥咸。

優(yōu)點(diǎn):

實(shí)現(xiàn)簡(jiǎn)單,不需要進(jìn)行對(duì)象進(jìn)行移動(dòng)细移。

缺點(diǎn):

標(biāo)記搏予、清除過程效率低,產(chǎn)生大量不連續(xù)的內(nèi)存碎片弧轧,提高了垃圾回收的頻率雪侥。

標(biāo)記壓縮算法

標(biāo)記壓縮算法可以說是標(biāo)記清除算法的優(yōu)化版 在標(biāo)記階段,從 GC Root 引用集合觸發(fā)去標(biāo)記所有對(duì)象精绎。在壓縮階段速缨,其則是將所有存活的對(duì)象壓縮在內(nèi)存的一邊,之后清理邊界外的所有空間代乃。

優(yōu)點(diǎn):

解決了標(biāo)記-清理算法存在的內(nèi)存碎片問題旬牲。

缺點(diǎn):

仍需要進(jìn)行局部對(duì)象移動(dòng),一定程度上降低了效率搁吓。

復(fù)制算法

復(fù)制算法的核心思想是將原有的內(nèi)存空間分為兩塊原茅,每次只使用一塊,在垃圾回收時(shí)擎浴,將正在使用的內(nèi)存中的存活對(duì)象復(fù)制到未使用的內(nèi)存塊中员咽。之后清除正在使用的內(nèi)存塊中的所有對(duì)象,之后交換兩個(gè)內(nèi)存塊的角色贮预,完成垃圾回收贝室。

優(yōu)點(diǎn):

按順序分配內(nèi)存即可,實(shí)現(xiàn)簡(jiǎn)單仿吞、運(yùn)行高效滑频,不用考慮內(nèi)存碎片。

缺點(diǎn):

可用的內(nèi)存大小縮小為原來的一半唤冈,對(duì)象存活率高時(shí)會(huì)頻繁進(jìn)行復(fù)制峡迷。

分代收集算法

JDK8堆內(nèi)存一般是劃分為年輕代和老年代,不同年代 根據(jù)自身特性采用不同的垃圾收集算法你虹。

對(duì)于老年代绘搞,因?yàn)閷?duì)象存活率高,沒有額外的內(nèi)存空間對(duì)它進(jìn)行擔(dān)保傅物。因而適合采用標(biāo)記-清理算法和標(biāo)記-整理算法進(jìn)行回收夯辖。試想一下,如果沒有采用分代算法董饰,而在老年代中使用復(fù)制算法蒿褂。在極端情況下圆米,老年代對(duì)象的存活率可以達(dá)到100%,那么我們就需要復(fù)制這么多個(gè)對(duì)象到另外一個(gè)內(nèi)存區(qū)域啄栓,這個(gè)工作量是非常龐大的娄帖。

對(duì)于新生代,每次GC時(shí)都有大量的對(duì)象死亡昙楚,只有少量對(duì)象存活近速。比較適合采用復(fù)制算法。這樣只需要復(fù)制少量對(duì)象桂肌,便可完成垃圾回收数焊,并且還不會(huì)有內(nèi)存碎片。

崎场,在實(shí)際的 JVM 新生代劃分中佩耳,卻不是采用等分為兩塊內(nèi)存的形式。而是分為:Eden 區(qū)域谭跨、from 區(qū)域干厚、to 區(qū)域 這三個(gè)區(qū)域。那么為什么 JVM 最終要采用這種形式螃宙,而不用 50% 等分為兩個(gè)內(nèi)存塊的方式蛮瞄?

要解答這個(gè)問題,我們就需要先深入了解新生代對(duì)象的特點(diǎn)谆扎。根據(jù)IBM公司的研究表明挂捅,在新生代中的對(duì)象 98% 是朝生夕死的,所以并不需要按照1:1的比例來劃分內(nèi)存空間堂湖。所以在HotSpot虛擬機(jī)中闲先,JVM 將內(nèi)存劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,其大小占比是8:1:1无蜂。當(dāng)回收時(shí)伺糠,將Eden和Survivor中還存活的對(duì)象一次性復(fù)制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Eden空間斥季。

通過這種方式训桶,內(nèi)存的空間利用率達(dá)到了90%,只有10%的空間是浪費(fèi)掉了酣倾。而如果通過均分為兩塊內(nèi)存舵揭,則其內(nèi)存利用率只有 50%,兩者利用率相差了將近一倍躁锡。

java編譯后是什么文件

https://www.cnblogs.com/chanshuyi/p/jvm_serial_04_from_source_code_to_machine_code.html

https://blog.csdn.net/qq_36791569/article/details/80269482

https://blog.csdn.net/q978090365/article/details/109465148

https://cloud.tencent.com/developer/article/1630650 javac 先將 Java 編譯成class字節(jié)碼文件
編譯完要執(zhí)行 通過解釋器解釋執(zhí)行和Jit編譯器轉(zhuǎn)為本地字節(jié)碼執(zhí)行 前者啟動(dòng)快運(yùn)行慢 后者啟動(dòng)慢運(yùn)行快 因?yàn)镴IT會(huì)將所有字節(jié)碼都轉(zhuǎn)化為機(jī)器碼并保存下來 而解釋器邊解釋邊運(yùn)行

Java9新特性AOT直接將class轉(zhuǎn)為二進(jìn)制可編譯文件 和JIT區(qū)別是 運(yùn)行前編譯好午绳,但缺點(diǎn)是全編譯 不用的也編譯了 不能動(dòng)態(tài)加載 但避免了JIT運(yùn)行時(shí)的內(nèi)存消耗

1.Java中創(chuàng)建線程的方式

1.繼承Thread類,重寫run方法

2.實(shí)現(xiàn)Runnable接口稚铣,傳遞給Thread(runnable)構(gòu)造函數(shù)

3.通過FutureTask 傳遞給Thread()構(gòu)造函數(shù)

CallableTest callableTest = new CallableTest();

FutureTask futureTask = new FutureTask<>(callableTest);

new Thread(futureTask).start();

創(chuàng)建FutureTask對(duì)象,創(chuàng)建Callable子類對(duì)象惕医,復(fù)寫call(相當(dāng)于run)方法

創(chuàng)建Thread類對(duì)象耕漱,將FutureTask對(duì)象傳遞給Thread對(duì)象

4.通過ExecutorService 線程池進(jìn)行創(chuàng)建多線程

Callable和Runnable的區(qū)別

(1) Callable重寫的是call()方法,Runnable重寫的方法是run()方法

(2) call()方法執(zhí)行后可以有返回值抬伺,run()方法沒有返回值.運(yùn)行Callable任務(wù)可以拿到一個(gè)Future對(duì)象螟够,表示異步計(jì)算的結(jié)果 。通過Future對(duì)象可以了解任務(wù)執(zhí)行情況峡钓,可取消任務(wù)的執(zhí)行妓笙,還可獲取執(zhí)行結(jié)果

(3) call()方法可以拋出異常,run()方法不可以

實(shí)現(xiàn)Runnable/Callable接口相比繼承Thread類的優(yōu)勢(shì)

由于Java“單繼承能岩,多實(shí)現(xiàn)”的特性寞宫,Runnable接口使用起來比Thread更靈活。

如果使用線程時(shí)不需要使用Thread類的諸多方法拉鹃,顯然使用Runnable接口更為輕量辈赋。

Callable如何使用

Callable一般是配合線程池工具ExecutorService來使用的,Callable一般是配合線程池工具ExecutorService來使用的 通過這個(gè)Future的get方法得到結(jié)果膏燕。

Future和FutureTask

https://zhuanlan.zhihu.com/p/38514871

Future接口只有幾個(gè)比較簡(jiǎn)單的方法:

public abstract interface Future { public abstract boolean cancel(boolean paramBoolean); public abstract boolean isCancelled(); public abstract boolean isDone(); public abstract V get() throws InterruptedException, ExecutionException; public abstract V get(long paramLong, TimeUnit paramTimeUnit) throws InterruptedException, ExecutionException, TimeoutException; }

也就是說Future提供了三種功能:

1)判斷任務(wù)是否完成钥屈;

2)能夠中斷任務(wù);

3)能夠獲取任務(wù)執(zhí)行結(jié)果坝辫。

因?yàn)镕uture只是一個(gè)接口篷就,所以是無法直接用來創(chuàng)建對(duì)象使用的,因此就有了下面的FutureTask近忙。

Future只是一個(gè)接口竭业,而它里面的cancel,get银锻,isDone等方法要自己實(shí)現(xiàn)起來都是非常復(fù)雜的永品。所以JDK提供了一個(gè)FutureTask類來供我們使用。

可以看出RunnableFuture繼承了Runnable接口和Future接口击纬,而FutureTask實(shí)現(xiàn)了RunnableFuture接口鼎姐。所以它既可以作為Runnable被線程執(zhí)行,又可以作為Future得到Callable的返回值更振。

2. 線程的幾種狀態(tài)

3. 談?wù)劸€程死鎖炕桨,如何有效的避免線程死鎖?

https://www.cnblogs.com/xiaoxi/p/8311034.html

什么是死鎖

死鎖 :在兩個(gè)或多個(gè)并發(fā)進(jìn)程中肯腕,如果每個(gè)進(jìn)程持有某種資源而又都等待別的進(jìn)程釋放它或它們現(xiàn)在保持著的資源献宫,在未改變這種狀態(tài)之前都不能向前推進(jìn),稱這一組進(jìn)程產(chǎn)生了死鎖

例如实撒,某計(jì)算機(jī)系統(tǒng)中只有一臺(tái)打印機(jī)和一臺(tái)輸入 設(shè)備姊途,進(jìn)程P1正占用輸入設(shè)備涉瘾,同時(shí)又提出使用打印機(jī)的請(qǐng)求,但此時(shí)打印機(jī)正被進(jìn)程P2 所占用捷兰,而P2在未釋放打印機(jī)之前立叛,又提出請(qǐng)求使用正被P1占用著的輸入設(shè)備。這樣兩個(gè)進(jìn)程相互無休止地等待下去贡茅,均無法繼續(xù)執(zhí)行秘蛇,此時(shí)兩個(gè)進(jìn)程陷入死鎖狀態(tài)。

死鎖產(chǎn)生的原因

  1. 系統(tǒng)資源的競(jìng)爭(zhēng)

通常系統(tǒng)中擁有的不可剝奪資源顶考,其數(shù)量不足以滿足多個(gè)進(jìn)程運(yùn)行的需要赁还,使得進(jìn)程在 運(yùn)行過程中,會(huì)因爭(zhēng)奪資源而陷入僵局驹沿。

  1. 進(jìn)程推進(jìn)順序非法

進(jìn)程在運(yùn)行過程中艘策,請(qǐng)求和釋放資源的順序不當(dāng),也同樣會(huì)導(dǎo)致死鎖渊季。例如柬焕,并發(fā)進(jìn)程 P1、P2分別保持了資源R1梭域、R2斑举,而進(jìn)程P1申請(qǐng)資源R2,進(jìn)程P2申請(qǐng)資源R1時(shí)病涨,兩者都 會(huì)因?yàn)樗栀Y源被占用而阻塞富玷。

3.信號(hào)量使用不當(dāng)也會(huì)造成死鎖。

進(jìn)程間彼此相互等待對(duì)方發(fā)來的消息既穆,結(jié)果也會(huì)使得這 些進(jìn)程間無法繼續(xù)向前推進(jìn)赎懦。例如,進(jìn)程A等待進(jìn)程B發(fā)的消息幻工,進(jìn)程B又在等待進(jìn)程A 發(fā)的消息励两,可以看出進(jìn)程A和B不是因?yàn)楦?jìng)爭(zhēng)同一資源,而是在等待對(duì)方的資源導(dǎo)致死鎖囊颅。

如何避免死鎖

1加鎖順序

線程按照一定的順序加鎖當(dāng)当悔,多個(gè)線程需要相同的一些鎖,但是按照不同的順序加鎖踢代,死鎖就很容易發(fā)生盲憎。如果能確保所有的線程都是按照相同的順序獲得鎖,那么死鎖就不會(huì)發(fā)生

2加鎖時(shí)限

在嘗試獲取鎖的時(shí)候加一個(gè)超時(shí)時(shí)間胳挎,超過時(shí)限則放棄對(duì)該鎖的請(qǐng)求饼疙,并釋放自己占有的鎖

3死鎖檢測(cè)

每當(dāng)一個(gè)線程獲得了鎖,會(huì)在線程和鎖相關(guān)的數(shù)據(jù)結(jié)構(gòu)中(map慕爬、graph等等)將其記下窑眯。除此之外屏积,每當(dāng)有線程請(qǐng)求鎖,也需要記錄在這個(gè)數(shù)據(jù)結(jié)構(gòu)中磅甩。

當(dāng)一個(gè)線程請(qǐng)求鎖失敗時(shí)肾请,這個(gè)線程可以遍歷鎖的關(guān)系圖看看是否有死鎖發(fā)生。例如更胖,線程A請(qǐng)求鎖7,但是鎖7這個(gè)時(shí)候被線程B持有隔显,這時(shí)線程A就可以檢查一下線程B是否已經(jīng)請(qǐng)求了線程A當(dāng)前所持有的鎖却妨。如果線程B確實(shí)有這樣的請(qǐng)求,那么就是發(fā)生了死鎖

那么當(dāng)檢測(cè)出死鎖時(shí)括眠,這些線程該做些什么呢彪标?

一個(gè)可行的做法是釋放所有鎖,回退掷豺,并且等待一段隨機(jī)的時(shí)間后重試捞烟。這個(gè)和簡(jiǎn)單的加鎖超時(shí)類似,不一樣的是只有死鎖已經(jīng)發(fā)生了才回退当船,而不會(huì)是因?yàn)榧渔i的請(qǐng)求超時(shí)了题画。

4. 如何實(shí)現(xiàn)多線程中的同步

synchronized對(duì)代碼塊或方法加鎖

reentrantLock加鎖結(jié)合Condition條件設(shè)置

volatile關(guān)鍵字

cas使用原子變量實(shí)現(xiàn)線程同步

參照UI線程更新UI的思路,使用handler把多線程的數(shù)據(jù)更新都集中在一個(gè)線程上德频,避免多線程出現(xiàn)臟讀

5. synchronized和Lock的使用苍息、區(qū)別,原理;

https://juejin.cn/post/6844903542440869896#heading-17

使用

synchronized

修飾實(shí)例方法

修飾靜態(tài)方法

修飾代碼塊

當(dāng)synchronized作用在實(shí)例方法時(shí)壹置,監(jiān)視器鎖(monitor)便是對(duì)象實(shí)例(this)竞思; 當(dāng)synchronized作用在靜態(tài)方法時(shí),監(jiān)視器鎖(monitor)便是對(duì)象的Class實(shí)例钞护,因?yàn)镃lass數(shù)據(jù)存在于永久代盖喷,因此靜態(tài)方法鎖相當(dāng)于該類的一個(gè)全局鎖; 當(dāng)synchronized作用在某一個(gè)對(duì)象實(shí)例時(shí)难咕,監(jiān)視器鎖(monitor)便是括號(hào)括起來的對(duì)象實(shí)例课梳;(https://www.cnblogs.com/aspirant/p/11470858.html)

類鎖:鎖是加持在類上的,用synchronized static 或者synchronized(class)方法使用的鎖都是類鎖余佃,因?yàn)閏lass和靜態(tài)方法在系統(tǒng)中只會(huì)產(chǎn)生一份惦界,所以在單系統(tǒng)環(huán)境中使用類鎖是線程安全的https://zhuanlan.zhihu.com/p/31537595

對(duì)象鎖:synchronized 修飾非靜態(tài)的方法和synchronized(this)都是使用的對(duì)象鎖,一個(gè)系統(tǒng)可以有多個(gè)對(duì)象實(shí)例咙冗,所以使用對(duì)象鎖不是線程安全的沾歪,除非保證一個(gè)系統(tǒng)該類型的對(duì)象只會(huì)創(chuàng)建一個(gè)(通常使用單例模式)才能保證線程安全;

lock

Lock和ReadWriteLock是兩大鎖的根接口,Lock代表實(shí)現(xiàn)類是ReentrantLock(可重入鎖),ReadWriteLock(讀寫鎖)的代表實(shí)現(xiàn)類是ReentrantReadWriteLock

Lock

lock()雾消、tryLock()灾搏、tryLock(long time, TimeUnit unit) 和 lockInterruptibly()都是用來獲取鎖的

lock:用來獲取鎖

unlock:釋放鎖 如果采用Lock挫望,必須主動(dòng)去釋放鎖,并且在發(fā)生異常時(shí)狂窑,不會(huì)自動(dòng)釋放鎖媳板。

tryLock:tryLock方法是有返回值的,它表示用來嘗試獲取鎖泉哈,如果獲取成功蛉幸,則返回true,如果獲取失敶曰蕖(即鎖已被其他線程獲绒热摇),則返回false烫沙,

lockInterruptibly:通過這個(gè)方法去獲取鎖時(shí)匹层,如果線程正在等待獲取鎖,則這個(gè)線程能夠響應(yīng)中斷锌蓄,即中斷線程的等待狀態(tài)升筏。

ReadWriteLock 接口只有兩個(gè)方法:

//返回用于讀取操作的鎖
Lock readLock()
//返回用于寫入操作的鎖
Lock writeLock()

ReetrantLock

可重入鎖又名遞歸鎖,是指在同一個(gè)線程在外層方法獲取鎖的時(shí)候瘸爽,再進(jìn)入該線程的內(nèi)層方法會(huì)自動(dòng)獲取鎖(前提鎖對(duì)象得是同一個(gè)對(duì)象或者class)您访,不會(huì)因?yàn)橹耙呀?jīng)獲取過還沒釋放而阻塞 Java中ReentrantLock和synchronized都是可重入鎖,可重入鎖的一個(gè)優(yōu)點(diǎn)是可一定程度避免死鎖剪决。

原理

首先ReentrantLock和NonReentrantLock都繼承父類AQS洋只,其父類AQS中維護(hù)了一個(gè)同步狀態(tài)status來計(jì)數(shù)重入次數(shù),status初始值為0昼捍。

當(dāng)線程嘗試獲取鎖時(shí)识虚,可重入鎖先嘗試獲取并更新status值,如果status == 0 則把status置為1妒茬,當(dāng)前線程開始執(zhí)行担锤。如果status != 0,則判斷當(dāng)前線程是否是獲取到這個(gè)鎖的線程乍钻,如果是的話執(zhí)行status+1肛循,且當(dāng)前線程可以再次獲取鎖。而非可重入鎖是 如果status != 0的話會(huì)導(dǎo)致其獲取鎖失敗银择,當(dāng)前線程阻塞多糠。

ReadWriteLock

https://www.cnblogs.com/myseries/p/10784076.html(使用)

共享鎖是指該鎖可被多個(gè)線程所持有。如果線程T對(duì)數(shù)據(jù)A加上共享鎖后浩考, 獲得共享鎖的線程只能讀數(shù)據(jù)夹孔,不能修改數(shù)據(jù)。

ReadWriteLock 維護(hù)了一對(duì)相關(guān)的鎖,一個(gè)用于只讀操作搭伤,另一個(gè)用于寫入操作

只要沒有 writer只怎,讀取鎖可以由多個(gè) reader 線程同時(shí)保持,而寫入鎖是獨(dú)占的

區(qū)別:

synchronized在發(fā)生異常時(shí)怜俐,會(huì)自動(dòng)釋放線程占有的鎖身堡,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生;而Lock在發(fā)生異常時(shí)拍鲤,如果沒有主動(dòng)通過unLock()去釋放鎖贴谎,則很可能造成死鎖現(xiàn)象,因此使用Lock時(shí)需要在finally塊中釋放鎖季稳;

Lock可以讓等待鎖的線程響應(yīng)中斷擅这,而synchronized卻不行

通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到绞幌。

原理

synchronized(https://juejin.cn/post/6844903670933356551#heading-6,https://juejin.cn/post/6844904181510176775#heading-6)

monitor描述為一種同步機(jī)制一忱,它通常被描述為一個(gè)對(duì)象莲蜘,當(dāng)一個(gè) monitor 被某個(gè)線程持有后,它便處于鎖定狀態(tài)

每個(gè)對(duì)象都存在著一個(gè) monitor 與之關(guān)聯(lián)

jvm基于進(jìn)入和退出Monitor對(duì)象來實(shí)現(xiàn)方法同步和代碼塊同步帘营。

對(duì)象頭和Monitor對(duì)象

對(duì)象頭

synchronized 用的鎖是存在Java對(duì)象頭里的

Hopspot 對(duì)象頭主要包括兩部分?jǐn)?shù)據(jù):Mark Word(標(biāo)記字段) 和 Klass Pointer(類型指針)

Mark Word:

Java 6 及其以后票渠,一個(gè)對(duì)象其實(shí)有四種鎖狀態(tài),它們級(jí)別由低到高依次是 無鎖狀態(tài) 偏向鎖狀態(tài) 輕量級(jí)鎖狀態(tài) 重量級(jí)鎖狀態(tài)

當(dāng)對(duì)象狀態(tài)為偏向鎖時(shí)芬迄,Mark Word存儲(chǔ)的是偏向的線程ID问顷; 當(dāng)狀態(tài)為輕量級(jí)鎖時(shí),Mark Word存儲(chǔ)的是指向棧中鎖記錄的指針禀梳; 當(dāng)狀態(tài)為重量級(jí)鎖時(shí)杜窄,Mark Word為指向堆中的monitor對(duì)象的指針

在HotSpot JVM實(shí)現(xiàn)中,鎖有個(gè)專門的名字:對(duì)象監(jiān)視器Object Monitor

同步代碼塊

從字節(jié)碼中可知同步語(yǔ)句塊的實(shí)現(xiàn)使用的是monitorenter和monitorexit指令

線程將試圖獲取對(duì)象鎖對(duì)應(yīng)的 monitor 的持有權(quán)算途, monitor的進(jìn)入計(jì)數(shù)器為 0塞耕,那線程可以成功取得monitor,并將計(jì)數(shù)器值設(shè)置為1嘴瓤,取鎖成功扫外。

同步方法

方法級(jí)的同步是隱式,即無需通過字節(jié)碼指令來控制的廓脆,它實(shí)現(xiàn)在方法調(diào)用和返回操作之中

當(dāng)方法調(diào)用時(shí)筛谚,調(diào)用指令將會(huì) 檢查方法的 訪問標(biāo)志是否被設(shè)置,如果設(shè)置了如果設(shè)置了停忿,執(zhí)行線程將先持有monitor(虛擬機(jī)規(guī)范中用的是管程一詞)驾讲,然后再執(zhí)行方法,執(zhí)行線程持有了monitor,其他任何線程都無法再獲得同一個(gè)monitor蝎毡。

6.volatile厚柳,synchronized和volatile的區(qū)別?為何不用volatile替代synchronized沐兵?

  1. 線程通信模型(http://concurrent.redspider.group/article/02/6.html)

內(nèi)存可見控制的是線程執(zhí)行結(jié)果在內(nèi)存中對(duì)其它線程的可見性别垮。根據(jù)Java內(nèi)存模型的實(shí)現(xiàn),線程在具體執(zhí)行時(shí)扎谎,會(huì)先拷貝主存數(shù)據(jù)到線程本地(CPU緩存)碳想,操作完成后再把結(jié)果從線程本地刷到主存

從抽象的角度來說,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系毁靶。

一般來說胧奔,JMM中的主內(nèi)存屬于共享數(shù)據(jù)區(qū)域,他是包含了堆和方法區(qū)预吆;同樣龙填,JMM中的本地內(nèi)存屬于私有數(shù)據(jù)區(qū)域,包含了程序計(jì)數(shù)器拐叉、本地方法棧岩遗、虛擬機(jī)棧。

根據(jù)JMM的規(guī)定凤瘦,線程對(duì)共享變量的所有操作都必須在自己的本地內(nèi)存中進(jìn)行宿礁,不能直接從主內(nèi)存中讀取。

所有的共享變量都存在主內(nèi)存中蔬芥。

每個(gè)線程都保存了一份該線程使用到的共享變量的副本梆靖。

如果線程A與線程B之間要通信的話,必須經(jīng)歷下面2個(gè)步驟: 線程A將本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去笔诵。 線程B到主內(nèi)存中去讀取線程A之前已經(jīng)更新過的共享變量返吻。

  1. volatile修飾的變量具有可見性

volatile關(guān)鍵字解決的是內(nèi)存可見性的問題,會(huì)使得所有對(duì)volatile變量的讀寫都會(huì)直接刷到主存乎婿,即保證了變量的可見性思喊。這樣就能滿足一些對(duì)變量可見性有要求而對(duì)讀取順序沒有要求的需求。

  1. volatile禁止指令重排

使用volatile關(guān)鍵字僅能實(shí)現(xiàn)對(duì)原始變量(如boolen次酌、 short 恨课、int 、long等)操作的原子性 是i++岳服,實(shí)際上也是由多個(gè)原子操作組成:read i; inc; write i剂公,假如多個(gè)線程同時(shí)執(zhí)行i++,volatile只能保證他們操作的i是同一塊內(nèi)存吊宋,但依然可能出現(xiàn)寫入臟數(shù)據(jù)的情況纲辽。

區(qū)別

https://blog.csdn.net/suifeng3051/article/details/52611233 https://juejin.cn/post/6844903598644543502#heading-1

volatile僅能使用在變量級(jí)別眼俊;synchronized則可以使用在變量轧邪、方法庐橙、和類級(jí)別的

volatile僅能實(shí)現(xiàn)變量的修改可見性琳水,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性

volatile不會(huì)造成線程的阻塞吊档;synchronized可能會(huì)造成線程的阻塞

7.鎖的分類篙议,鎖的幾種狀態(tài),CAS原理

https://juejin.cn/post/6844904181510176775#heading-8 https://tech.meituan.com/2018/11/15/java-lock.html

無鎖/偏向鎖/輕量級(jí)鎖/重量級(jí)鎖

Hotspot的作者經(jīng)過以往的研究發(fā)現(xiàn)大多數(shù)情況下鎖不僅不存在多線程競(jìng)爭(zhēng)怠硼,而且總是由同一線程多次獲得鬼贱,為了讓線程獲得鎖的代價(jià)更低而引入了偏向鎖。

四種狀態(tài)的轉(zhuǎn)換(http://concurrent.redspider.group/article/02/9.html)

1.每一個(gè)線程在準(zhǔn)備獲取共享資源時(shí): 第一步香璃,檢查MarkWord里面是不是放的自己的ThreadId ,如果是这难,表示當(dāng)前線程是處于 “偏向鎖” 。

2.第二步葡秒,如果MarkWord不是自己的ThreadId姻乓,會(huì)嘗試使用CAS來替換Mark Word里面的線程ID為新線程的ID,成功眯牧,仍然為偏向鎖蹋岩,失敗,升級(jí)為輕量級(jí)鎖炸站,會(huì)按照輕量級(jí)鎖的方式進(jìn)行競(jìng)爭(zhēng)鎖星澳。

3.CAS將鎖的Mark Word替換為指向鎖記錄的指針疚顷,成功的獲得資源旱易,失敗的則進(jìn)入自旋 。

4.自旋的線程在自旋過程中腿堤,成功獲得資源(即之前獲的資源的線程執(zhí)行完成并釋放了共享資源)阀坏,則整個(gè)狀態(tài)依然處于 輕量級(jí)鎖的狀態(tài),如果自旋失敗笆檀,進(jìn)入重量級(jí)鎖的狀態(tài)忌堂,這個(gè)時(shí)候,自旋的線程進(jìn)行阻塞酗洒,等待之前線程執(zhí)行完成并喚醒自己士修。

樂觀鎖/悲觀鎖

對(duì)于同一個(gè)數(shù)據(jù)的并發(fā)操作,悲觀鎖認(rèn)為自己在使用數(shù)據(jù)的時(shí)候一定有別的線程來修改數(shù)據(jù)樱衷,因此在獲取數(shù)據(jù)的時(shí)候會(huì)先加鎖棋嘲,確保數(shù)據(jù)不會(huì)被別的線程修改

而樂觀鎖認(rèn)為自己在使用數(shù)據(jù)時(shí)不會(huì)有別的線程修改數(shù)據(jù),所以不會(huì)添加鎖矩桂,只是在更新數(shù)據(jù)的時(shí)候去判斷之前有沒有別的線程更新了這個(gè)數(shù)據(jù)沸移。如果這個(gè)數(shù)據(jù)沒有被更新,當(dāng)前線程將自己修改的數(shù)據(jù)成功寫入。如果數(shù)據(jù)已經(jīng)被其他線程更新雹锣,則根據(jù)不同的實(shí)現(xiàn)方式執(zhí)行不同的操作(例如報(bào)錯(cuò)或者自動(dòng)重試)

悲觀鎖適合寫操作多的場(chǎng)景网沾,先加鎖可以保證寫操作時(shí)數(shù)據(jù)正確。 synchronized關(guān)鍵字和Lock的實(shí)現(xiàn)類都是悲觀鎖蕊爵。

樂觀鎖適合讀操作多的場(chǎng)景辉哥,不加鎖的特點(diǎn)能夠使其讀操作的性能大幅提升。 java.util.concurrent包中的原子類就是通過CAS來實(shí)現(xiàn)了樂觀鎖在辆。

為何樂觀鎖能夠做到不鎖定同步資源也可以正確的實(shí)現(xiàn)線程同步呢

CAS全稱 Compare And Swap(比較與交換)证薇,是一種無鎖算法。在不使用鎖(沒有線程被阻塞)的情況下實(shí)現(xiàn)多線程之間的變量同步匆篓。java.util.concurrent包中的原子類就是通過CAS來實(shí)現(xiàn)了樂觀鎖浑度。

CAS算法涉及到三個(gè)操作數(shù):

進(jìn)行比較的值 A。 要寫入的新值 B鸦概。 需要讀寫的內(nèi)存值 C箩张。

AtomicInteger的自增函數(shù)incrementAndGet()的源碼時(shí)

去比較寄存器中的 A 和 內(nèi)存中的值 V。如果相等窗市,就把要寫入的新值 B 存入內(nèi)存中先慷。如果不相等,就將內(nèi)存值 V 賦值給寄存器中的值 A咨察。然后通過Java代碼中的while循環(huán)再次調(diào)用cmpxchg指令進(jìn)行重試论熙,直到設(shè)置成功為止

可重入鎖

可重入鎖又名遞歸鎖,是指在同一個(gè)線程在外層方法獲取鎖的時(shí)候摄狱,再進(jìn)入該線程的內(nèi)層方法會(huì)自動(dòng)獲取鎖(前提鎖對(duì)象得是同一個(gè)對(duì)象或者class)脓诡,不會(huì)因?yàn)橹耙呀?jīng)獲取過還沒釋放而阻塞 Java中ReentrantLock和synchronized都是可重入鎖,可重入鎖的一個(gè)優(yōu)點(diǎn)是可一定程度避免死鎖媒役。

自旋鎖

阻塞或喚醒一個(gè)Java線程需要操作系統(tǒng)切換CPU狀態(tài)來完成祝谚,這種狀態(tài)轉(zhuǎn)換需要耗費(fèi)處理器時(shí)間。如果同步代碼塊中的內(nèi)容過于簡(jiǎn)單酣衷,狀態(tài)轉(zhuǎn)換消耗的時(shí)間有可能比用戶代碼執(zhí)行的時(shí)間還要長(zhǎng)交惯。

需讓當(dāng)前線程進(jìn)行自旋,如果在自旋完成后前面鎖定同步資源的線程已經(jīng)釋放了鎖穿仪,那么當(dāng)前線程就可以不必阻塞而是直接獲取同步資源席爽,從而避免切換線程的開銷。這就是自旋鎖啊片。

8.為什么會(huì)有線程安全只锻?如何保證線程安全

https://github.com/Moosphan/Android-Daily-Interview/issues/108

在多個(gè)線程訪問共同資源時(shí),在某一個(gè)線程對(duì)資源進(jìn)行寫操作中途(寫入已經(jīng)開始,還沒結(jié)束),其他線程對(duì)這個(gè)寫了一般資源進(jìn)行了讀操作,或者基于這個(gè)寫了一半操作進(jìn)行寫操作,導(dǎo)致數(shù)據(jù)問題

原子性(java.util.concurrent.atomic 包)

即一個(gè)操作或者多個(gè)操作 要么全部執(zhí)行并且執(zhí)行的過程不會(huì)被任何因素打斷,要么就都不執(zhí)行

有序性( Synchronized, Lock)

即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行

可見性(Volatile)

指當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí)钠龙,一個(gè)線程修改了這個(gè)變量的值炬藤,其他線程能夠立即看得到修改的值御铃。

9.sleep()與wait()區(qū)別,run和start的區(qū)別,notify和notifyall區(qū)別,鎖池,等待池

https://blog.csdn.net/u012050154/article/details/50903326 https://github.com/Moosphan/Android-Daily-Interview/issues/117 sleep()方法正在執(zhí)行的線程主動(dòng)讓出CPU(然后CPU就可以去執(zhí)行其他任務(wù)),而并不會(huì)釋放同步資源鎖!I蚩蟆上真!

wait()方法則是指當(dāng)前線程讓自己暫時(shí)退讓出同步資源鎖,只有調(diào)用了notify()方法羹膳,之前調(diào)用wait()的線程才會(huì)解除wait狀態(tài)

sleep不需要喚醒睡互,wait需要喚醒

1.run和start的區(qū)別

start() 可以啟動(dòng)一個(gè)新線程,run()不會(huì)

tart()中的run代碼可以不執(zhí)行完就繼續(xù)執(zhí)行下面的代碼 直接調(diào)用run方法必須等待其代碼全部執(zhí)行完才能繼續(xù)執(zhí)行下面的代碼陵像。

2.sleep wait區(qū)別

sleep是Thread方法就珠,不會(huì)釋放鎖,wait是obje方法醒颖,會(huì)釋放鎖

sleep不需要Synchronized 妻怎,wait需要Synchronized

sleep休眠自動(dòng)繼續(xù)執(zhí)行,wait需要notify來喚醒泞歉,得到鎖

3.notify和notifyall區(qū)別

notify是只喚醒一個(gè)等待的線程逼侦,noftifyALl是喚醒所有等待的線程

notify后只要一個(gè)線程會(huì)由等待池進(jìn)入鎖池,而notifyAll會(huì)將該對(duì)象等待池內(nèi)的所有線程移動(dòng)到鎖池中腰耙,等待鎖競(jìng)爭(zhēng)

某個(gè)對(duì)象的wait()方法榛丢,線程A就會(huì)釋放該對(duì)象的鎖后,進(jìn)入到了該對(duì)象的等待池中

https://www.zhihu.com/question/37601861/answer/145545371

鎖池:假設(shè)線程A已經(jīng)擁有了某個(gè)對(duì)象(注意:不是類)的鎖挺庞,而其它的線程想要調(diào)用這個(gè)對(duì)象的某個(gè)synchronized方法(或者synchronized塊)晰赞,由于這些線程在進(jìn)入對(duì)象的synchronized方法之前必須先獲得該對(duì)象的鎖的擁有權(quán),但是該對(duì)象的鎖目前正被線程A擁有选侨,所以這些線程就進(jìn)入了該對(duì)象的鎖池中掖鱼。

等待池:假設(shè)一個(gè)線程A調(diào)用了某個(gè)對(duì)象的wait()方法,線程A就會(huì)釋放該對(duì)象的鎖后侵俗,進(jìn)入到了該對(duì)象的等待池中

10.Java多線程通信

http://concurrent.redspider.group/article/01/2.html

11.Java多線程

https://blog.csdn.net/weixin_40271838/article/details/79998327

http://concurrent.redspider.group/article/03/12.html

Java中開辟出了一種管理線程的概念锨用,這個(gè)概念叫做線程池丰刊,多次使用線程隘谣,也就意味著,我們需要多次創(chuàng)建并銷毀線程啄巧。而創(chuàng)建并銷毀線程的過程勢(shì)必會(huì)消耗內(nèi)存寻歧。

好處

創(chuàng)建/銷毀線程需要消耗系統(tǒng)資源,線程池可以復(fù)用已創(chuàng)建的線程秩仆。

控制并發(fā)的數(shù)量码泛。并發(fā)數(shù)量過多,可能會(huì)導(dǎo)致資源消耗過多澄耍,從而造成服務(wù)器崩潰噪珊。(主要原因)

可以對(duì)線程做統(tǒng)一管理晌缘。

參數(shù)

https://blog.csdn.net/weixin_40271838/article/details/79998327

線程池中的corePoolSize就是線程池中的核心線程數(shù)量,這幾個(gè)核心線程痢站,只是在沒有用的時(shí)候磷箕,也不會(huì)被回收 maximumPoolSize就是線程池中可以容納的最大線程的數(shù)量 keepAliveTime,就是線程池中除了核心線程之外的其他的最長(zhǎng)可以保留的時(shí)間 TimeUnit unit:keepAliveTime的單位 BlockingQueue workQueue:阻塞隊(duì)列阵难,任務(wù)可以儲(chǔ)存在任務(wù)隊(duì)列中等待被執(zhí)行岳枷。

兩個(gè)非必須的參

ThreadFactory threadFactory

創(chuàng)建線程的工廠 ,用于批量創(chuàng)建線程呜叫,統(tǒng)一在創(chuàng)建線程時(shí)設(shè)置一些參數(shù)空繁,如是否守護(hù)線程、線程的優(yōu)先級(jí)

RejectedExecutionHandler handler

拒絕處理策略朱庆,線程數(shù)量大于最大線程數(shù)就會(huì)采用拒絕處理策略

Java中的線程池共有幾種

http://www.reibang.com/p/5936a2242322

CachedThreadPool: 該線程池中沒有核心線程盛泡,非核心線程的數(shù)量為Integer.max_value,就是無限大娱颊,當(dāng)有需要時(shí)創(chuàng)建線程來執(zhí)行任務(wù)饭于,沒有需要時(shí)回收線程,適用于耗時(shí)少维蒙,任務(wù)量大的情況掰吕。SynchronousQueue

FixedThreadPool:定長(zhǎng)的線程池,有核心線程颅痊,核心線程的即為最大的線程數(shù)量殖熟,沒有非核心線程LinkedBlockingQueue

SecudleThreadPool:創(chuàng)建一個(gè)定長(zhǎng)線程池,支持定時(shí)及周期性任務(wù)執(zhí)行斑响。

SingleThreadPool:只有一條線程來執(zhí)行任務(wù)菱属,使用了LinkedBlockingQueue(容量很大),所以舰罚,不會(huì)創(chuàng)建非核心線程纽门。所有任務(wù)按照先來先執(zhí)行的順序執(zhí)行LinkedBlockingQueue

何為阻塞隊(duì)列?

http://concurrent.redspider.group/article/03/13.html

生產(chǎn)者-消費(fèi)者模式 生產(chǎn)者一直生產(chǎn)資源营罢,消費(fèi)者一直消費(fèi)資源赏陵,資源存儲(chǔ)在一個(gè)緩沖池中,生產(chǎn)者將生產(chǎn)的資源存進(jìn)緩沖池中饲漾,消費(fèi)者從緩沖池中拿到資源進(jìn)行消費(fèi)蝙搔。

我們自己coding實(shí)現(xiàn)這個(gè)模式的時(shí)候,因?yàn)樾枰尪鄠€(gè)線程操作共享變量(即資源)考传,所以很容易引發(fā)線程安全問題吃型,造成重復(fù)消費(fèi)和死鎖。另外僚楞,當(dāng)緩沖池空了勤晚,我們需要阻塞消費(fèi)者枉层,喚醒生產(chǎn)者;當(dāng)緩沖池滿了赐写,我們需要阻塞生產(chǎn)者返干,喚醒消費(fèi)者,這些個(gè)等待-喚醒邏輯都需要自己實(shí)現(xiàn)血淌。

阻塞隊(duì)列(BlockingQueue)矩欠,你只管往里面存、取就行悠夯,而不用擔(dān)心多線程環(huán)境下存癌淮、取共享變量的線程安全問題。

阻塞隊(duì)列的原理很簡(jiǎn)單沦补,利用了Lock鎖的多條件(Condition)阻塞控制

put和take操作都需要先獲取鎖乳蓄,沒有獲取到鎖的線程會(huì)被擋在第一道大門之外自旋拿鎖,直到獲取到鎖夕膀。

就算拿到鎖了之后虚倒,也不一定會(huì)順利進(jìn)行put/take操作,需要判斷隊(duì)列是否可用(是否滿/空)产舞,如果不可用魂奥,則會(huì)被阻塞,并釋放鎖易猫。

有哪幾種工作隊(duì)列

1耻煤、LinkedBlockingQueue

一個(gè)基于鏈表結(jié)構(gòu)的阻塞隊(duì)列,此隊(duì)列按FIFO (先進(jìn)先出) 排序元素准颓,吞吐量通常要高于ArrayBlockingQueue哈蝇。靜態(tài)工廠方法Executors.newFixedThreadPool()和Executors.newSingleThreadExecutor使用了這個(gè)隊(duì)列。

2攘已、SynchronousQueue

一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列炮赦。每個(gè)插入操作必須等到另一個(gè)線程調(diào)用移除操作,否則插入操作一直處于阻塞狀態(tài)样勃,吞吐量通常要高于LinkedBlockingQueue吠勘,靜態(tài)工廠方法Executors.newCachedThreadPool使用了這個(gè)隊(duì)列。

線程池原理

從數(shù)據(jù)結(jié)構(gòu)的角度來看彤灶,線程池主要使用了阻塞隊(duì)列(BlockingQueue)

1看幼、如果正在運(yùn)行的線程數(shù) < coreSize批旺,馬上創(chuàng)建核心線程執(zhí)行該task幌陕,不排隊(duì)等待; 2汽煮、如果正在運(yùn)行的線程數(shù) >= coreSize搏熄,把該task放入阻塞隊(duì)列棚唆; 3、如果隊(duì)列已滿 && 正在運(yùn)行的線程數(shù) < maximumPoolSize心例,創(chuàng)建新的非核心線程執(zhí)行該task宵凌; 4、如果隊(duì)列已滿 && 正在運(yùn)行的線程數(shù) >= maximumPoolSize止后,線程池調(diào)用handler的reject方法拒絕本次提交瞎惫。

理解記憶:1-2-3-4對(duì)應(yīng)(核心線程->阻塞隊(duì)列->非核心線程->handler拒絕提交)。

https://juejin.cn/post/6844903511809851400 1.注解的分類

Java的注解分為元注解和標(biāo)準(zhǔn)注解译株。

標(biāo)準(zhǔn)注解 SDO

元注解

@Documented

是一個(gè)標(biāo)記注解瓜喇,沒有成員變量。 會(huì)被JavaDoc 工具提取成文檔

@Target(METHOD,TYPE類,FIELD用于成員變量,PACKAGE用于包) 表示被描述的注解用于什么地方

@Retention

描述注解的生命周期

SOURCE:在源文件中有效(即源文件保留) CLASS:在 class 文件中有效(即 class 保留) RUNTIME:在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留)

這3個(gè)生命周期分別對(duì)應(yīng)于:Java源文件(.java文件) ---> .class文件 ---> 內(nèi)存中的字節(jié)碼歉糜。

1乘寒、RetentionPolicy.SOURCE:注解只保留在源文件,當(dāng)Java文件編譯成class文件的時(shí)候匪补,注解被遺棄伞辛; 2、RetentionPolicy.CLASS:注解被保留到class文件夯缺,但jvm加載class文件時(shí)候被遺棄蚤氏,這是默認(rèn)的生命周期; 3踊兜、RetentionPolicy.RUNTIME:注解不僅被保存到class文件中瞧捌,jvm加載class文件之后,仍然存在润文;

@Documented – 注解是否將包含在JavaDoc中 @Retention – 什么時(shí)候使用該注解 @Target – 注解用于什么地方 @Inherited – 是否允許子類繼承該注解

注解的底層實(shí)現(xiàn)原理

注解的原理:

注解本質(zhì)是一個(gè)繼承了Annotation 的特殊接口姐呐,其具體實(shí)現(xiàn)類是Java 運(yùn)行時(shí)生成的動(dòng)態(tài)代理類。

而我們通過反射獲取注解時(shí)典蝌,返回的是Java 運(yùn)行時(shí)生成的動(dòng)態(tài)代理對(duì)象$Proxy1曙砂。

通過代理對(duì)象調(diào)用自定義注解(接口)的方法,會(huì)最終調(diào)用AnnotationInvocationHandler 的invoke方法骏掀。

1.什么是反射

JAVA反射機(jī)制是在運(yùn)行狀態(tài)中鸠澈,

對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法截驮;

對(duì)于任意一個(gè)對(duì)象笑陈,都能夠調(diào)用它的任意方法和屬性;

這種動(dòng)態(tài)獲取信息以及動(dòng)態(tài)調(diào)用對(duì)象方法的功能稱為java語(yǔ)言的反射機(jī)制葵袭。

2.反射機(jī)制的相關(guān)類

類名 用途

Class類 代表類的實(shí)體涵妥,在運(yùn)行的Java應(yīng)用程序中表示類和接口

Field類 代表類的成員變量(成員變量也稱為類的屬性)

Constructor類 代表類的構(gòu)造方法

Method類 代表類的方法

3.反射中如何獲取Class類的實(shí)例

(1)通過.class

Class class1 = Person.class;

(2)通過創(chuàng)建實(shí)例對(duì)象來獲取類對(duì)象

Person person =new Person();

Class class2 = person.getClass();

(3) 通過包名,調(diào)用class的forName方法

Class class3 = Class.forName("day07.Person");

4.如何獲取一個(gè)類的屬性對(duì)象 & 構(gòu)造器對(duì)象 & 方法對(duì)象 坡锡。

通過class對(duì)象創(chuàng)建一個(gè)實(shí)例對(duì)象

Cls.newInstance();

通過class對(duì)象獲得一個(gè)屬性對(duì)象

Field c=cls.getFields():

獲得某個(gè)類的所有的公共(public)的字段蓬网,包括父類中的字段窒所。

Field c=cls.getDeclaredFields():

獲得某個(gè)類的所有聲明的字段,即包括public帆锋、private和proteced吵取,但是不包括父類的聲明字段

獲取構(gòu)造器對(duì)象

Clazz.getConstructor();

通過class對(duì)象獲得一個(gè)方法對(duì)象

Cls.getMethod(“方法名”,class……parameaType);(只能獲取公共的)

Cls.getDeclareMethod(“方法名”);(獲取任意修飾的方法,不能執(zhí)行私有)

5.Class.getField和getDeclaredField的區(qū)別锯厢,getDeclaredMethod和getMethod的區(qū)別

getField

獲取一個(gè)類的 ==public成員變量皮官,包括基類== 。

getDeclaredField

獲取一個(gè)類的 ==所有成員變量实辑,不包括基類== 臣疑。

getDeclaredMethod*()

獲取的是類自身聲明的所有方法,包含public徙菠、protected和private方法讯沈。

getMethod*()

獲取的是類的所有共有方法,這就包括自身的所有public方法婿奔,和從基類繼承的缺狠、從接口實(shí)現(xiàn)的所有public方法 現(xiàn)的所有public方法。 萍摊。

6.反射機(jī)制的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

1)能夠運(yùn)行時(shí)動(dòng)態(tài)獲取類的實(shí)例挤茄,提高靈活性;

缺點(diǎn):

1)使用反射性能較低冰木,需要解析字節(jié)碼穷劈,將內(nèi)存中的對(duì)象進(jìn)行解析。

2)相對(duì)不安全踊沸,破壞了封裝性(因?yàn)橥ㄟ^反射可以獲得私有方法和屬性)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末歇终,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子逼龟,更是在濱河造成了極大的恐慌评凝,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腺律,死亡現(xiàn)場(chǎng)離奇詭異奕短,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)匀钧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門翎碑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人之斯,你說我怎么就攤上這事日杈。” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵达椰,是天一觀的道長(zhǎng)翰蠢。 經(jīng)常有香客問我项乒,道長(zhǎng)啰劲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任檀何,我火速辦了婚禮蝇裤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘频鉴。我一直安慰自己栓辜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布垛孔。 她就那樣靜靜地躺著藕甩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪周荐。 梳的紋絲不亂的頭發(fā)上狭莱,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音概作,去河邊找鬼腋妙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛讯榕,可吹牛的內(nèi)容都是我干的骤素。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼愚屁,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼济竹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起霎槐,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤规辱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后栽燕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罕袋,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年碍岔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浴讯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蔼啦,死狀恐怖榆纽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤奈籽,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布饥侵,位于F島的核電站,受9級(jí)特大地震影響衣屏,放射性物質(zhì)發(fā)生泄漏躏升。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一狼忱、第九天 我趴在偏房一處隱蔽的房頂上張望膨疏。 院中可真熱鬧,春花似錦钻弄、人聲如沸佃却。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)饲帅。三九已至,卻和暖如春瘤泪,著一層夾襖步出監(jiān)牢的瞬間灶泵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工均芽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丘逸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓掀宋,卻偏偏與公主長(zhǎng)得像深纲,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子劲妙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 什么是程序湃鹊,進(jìn)程和線程? 程序是計(jì)算機(jī)的可執(zhí)行文件 進(jìn)程是計(jì)算機(jī)資源分配的基本單位 線程是資源調(diào)度執(zhí)行的基本單位一...
    碼農(nóng)Kkio閱讀 246評(píng)論 0 6
  • 1镣奋、HashMap的put方法處理邏輯以及線程不安全體現(xiàn)的場(chǎng)景币呵,基于HashMap實(shí)現(xiàn)線程安全該怎么改代碼,has...
    w孤風(fēng)閱讀 640評(píng)論 0 1
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月侨颈,有人笑有人哭余赢,有人歡樂有人憂愁,有人驚喜有人失落哈垢,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,536評(píng)論 28 53
  • 人工智能是什么妻柒?什么是人工智能?人工智能是未來發(fā)展的必然趨勢(shì)嗎耘分?以后人工智能技術(shù)真的能達(dá)到電影里機(jī)器人的智能水平嗎...
    ZLLZ閱讀 3,781評(píng)論 0 5
  • 首先介紹下自己的背景: 我11年左右入市到現(xiàn)在,也差不多有4年時(shí)間央渣,看過一些關(guān)于股票投資的書籍计盒,對(duì)于巴菲特等股神的...
    瞎投資閱讀 5,731評(píng)論 3 8