前言
之前在刷博客的時候聂儒,發(fā)現(xiàn)一些寫得比較好的博客都會默默收藏起來。最近在查閱補漏硫痰,有的知識點比較重要的衩婚,但是在之前的博客中還沒有寫到,于是趁著閑整理一下效斑。
文本的知識點:
- Integer常量池
- TCP拆包粘包
-
select非春、poll、epoll
簡單區(qū)別 - jdk1.6以后對Synchronize鎖優(yōu)化
- Java內(nèi)存模型
本文力求簡單講清每個知識點缓屠,希望大家看完能有所收獲
一奇昙、神奇的Integer
前陣子在群上看有人在討論關(guān)于Integer的true或者false問題,我本以為我已經(jīng)懂了這方面的知識點了敌完。但還是做錯了储耐,后來去請教了一下朋友。朋友又給我發(fā)了另一張圖:
后來發(fā)現(xiàn)這是出自《深入理解Java虛擬機——JVM高級特性與最佳實踐(第2版)》中的10.3.2小節(jié)中~
public class Main_1 {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
System.out.println(c == d);
System.out.println(e == f);
System.out.println(c == (a + b));
System.out.println(c.equals(a + b));
System.out.println(g == (a + b));
System.out.println(g.equals(a + b));
System.out.println(g.equals(a + h));
}
}
你們可以先思考一下再往下翻看答案滨溉,看看能不能做對什湘。
1.1解題思路
在解這道題之前,相信很多人都已經(jīng)知道了晦攒,在Java中會有一個Integer緩存池禽炬,緩存的大小是:-128~127
答案是:
- true
- false
- true
- true
- true
- false
- true
簡單解釋一下:
- 使用
==
的情況:- 如果比較Integer變量,默認比較的是地址值勤家。
- Java的Integer維護了從
-128~127
的緩存池 - 如果比較的某一邊有操作表達式(例如a+b)腹尖,那么比較的是具體數(shù)值
- 使用
equals()
的情況:- 無論是Integer還是Long中的
equals()
默認比較的是數(shù)值。 - Long的
equals()
方法,JDK的默認實現(xiàn):會判斷是否是Long類型
- 無論是Integer還是Long中的
- 注意自動拆箱热幔,自動裝箱問題乐设。
反編譯一下看看:
import java.io.PrintStream;
public class Main_1 {
public static void main(String[] paramArrayOfString) {
Integer localInteger1 = Integer.valueOf(1);
Integer localInteger2 = Integer.valueOf(2);
Integer localInteger3 = Integer.valueOf(3);
Integer localInteger4 = Integer.valueOf(3);
Integer localInteger5 = Integer.valueOf(321);
Integer localInteger6 = Integer.valueOf(321);
Long localLong = Long.valueOf(3L);
// 緩存池
System.out.println(localInteger3 == localInteger4);
// 超出緩存池范圍
System.out.println(localInteger5 == localInteger6);
// 存在a+b數(shù)值表達式,比較的是數(shù)值
System.out.println(localInteger3.intValue() == localInteger1.intValue() + localInteger2.intValue());
// equals比較的是數(shù)值
System.out.println(localInteger3.equals(Integer.valueOf(localInteger1.intValue() + localInteger2.intValue())));
// 存在a+b數(shù)值表達式绎巨,比較的是數(shù)值
System.out.println(localLong.longValue() == localInteger1.intValue() + localInteger2.intValue());
// Long的equals()先判斷傳遞進來的是不是Long類型近尚,而a+b自動裝箱的是Integer類型
System.out.println(localLong.equals(Integer.valueOf(localInteger1.intValue() + localInteger2.intValue())));
// ... 最后一句在這里漏掉了,大家應該可以推斷出來
}
}
我使用的反編譯工具是jd-gui
场勤,如果還沒有試過反編譯的同學可以下載來玩玩:
二戈锻、Synchronize鎖優(yōu)化手段有哪些
之前在寫多線程文章的時候,簡單說了一下synchronized鎖在jdk1.6以后會有各種的優(yōu)化:適應自旋鎖和媳,鎖消除格遭,鎖粗化,輕量級鎖留瞳,偏向鎖拒迅。
本以為這些優(yōu)化是非常難以理解的東西,其實不然~~~簡單了解一下還是很好理解的她倘。
2.1適應自旋鎖
鎖競爭是kernal mode下的璧微,會經(jīng)過user mode(用戶態(tài))到kernal mode(內(nèi)核態(tài)) 的切換,是比較花時間的硬梁。
自旋鎖出現(xiàn)的原因是人們發(fā)現(xiàn)大多數(shù)時候鎖的占用只會持續(xù)很短的時間前硫,甚至低于切換到kernal mode所花的時間,所以在進入kernal mode前讓線程等待有限的時間荧止,如果在此時間內(nèi)能夠獲取到鎖就避免了很多無謂的時間开瞭,若不能則再進入kernal mode競爭鎖。
在JDK 1.6中引入了自適應的自旋鎖罩息,說明自旋的時間不固定嗤详,要不要自旋變得越來越聰明。
自旋鎖在JDK1.4.2中就已經(jīng)引入瓷炮,只不過默認是關(guān)閉的葱色,可以使用-XX:+UseSpinning
參數(shù)來開啟,在JDK1.6中就已經(jīng)改為默認開啟了娘香。
參考資料:
- 自旋鎖和使線程休眠的非自旋鎖各有什么適用場景苍狰?www.zhihu.com/question/38…
2.2鎖消除
如果JVM明顯檢測到某段代碼是線程安全的(言外之意:無鎖也是安全的),JVM會安全地原有的鎖消除掉烘绽!
比如說:
public void vectorTest(){
Vector<String> vector = new Vector<String>();
for(int i = 0 ; i < 10 ; i++){
vector.add(i + "");
}
System.out.println(vector);
}
Vector是默認加鎖的淋昭,但JVM如果發(fā)現(xiàn)vector變量僅僅在vectorTest()
方法中使用,那該vector是線程安全的安接。JVM會把vector內(nèi)部加的鎖去除翔忽,這個優(yōu)化就叫做:鎖消除。
2.3鎖粗化
默認情況下,總是推薦將同步塊的作用范圍限制得盡量小歇式。
但是如果一系列的連續(xù)操作都對同一個對象反復加鎖和解鎖驶悟,甚至加鎖操作是出現(xiàn)在循環(huán)體中的,頻繁地進行互斥同步操作也會導致不必要的性能損耗材失。
JVM會將加鎖的范圍擴展(粗化)痕鳍,這就叫做鎖粗化。
2.4輕量級鎖
輕量級鎖能提升程序同步性能的依據(jù)是“對于絕大部分的鎖龙巨,在整個同步周期內(nèi)都是不存在競爭的”笼呆,這是一個經(jīng)驗數(shù)據(jù)。
- 如果沒有競爭旨别,輕量級鎖使用CAS操作避免了使用互斥量的開銷
- 但如果存在鎖競爭诗赌,除了互斥量的開銷外,還額外發(fā)生了CAS操作昼榛,因此在有競爭的情況下境肾,輕量級鎖會比傳統(tǒng)的重量級鎖更慢剔难。
簡單來說:如果發(fā)現(xiàn)同步周期內(nèi)都是不存在競爭胆屿,JVM會使用CAS操作來替代操作系統(tǒng)互斥量。這個優(yōu)化就被叫做輕量級鎖偶宫。
2.5偏向鎖
偏向鎖就是在無競爭的情況下把整個同步都消除掉非迹,連CAS操作都不做了!
偏向鎖可以提高帶有同步但無競爭的程序性能纯趋。它同樣是一個帶有效益權(quán)衡(Trade Off)性質(zhì)的優(yōu)化憎兽,也就是說,它并不一定總是對程序運行有利吵冒,如果程序中大多數(shù)的鎖總是被多個不同的線程訪問纯命,那偏向模式就是多余的。在具體問題具體分析的前提下痹栖,有時候使用參數(shù)
-XX:-UseBiasedLocking
來禁止偏向鎖優(yōu)化反而可以提升性能亿汞。
2.6簡單總結(jié)各種鎖優(yōu)化
- 自適應偏向鎖:自旋時間不固定
- 鎖消除:如果發(fā)現(xiàn)代碼是線程安全的,將鎖去掉
- 鎖粗化:加鎖范圍過小(重復加鎖)揪阿,將加鎖的范圍擴展
- 輕量級鎖:在無競爭的情況下使用CAS操作去消除同步使用的互斥量
- 偏向鎖:在無競爭環(huán)境下疗我,把整個同步都消除,CAS也不做南捂。
參考資料:
三吴裤、TCP粘包,拆包
這是在看wangjingxin大佬面經(jīng)的時候看到的面試題溺健,之前對TCP粘包麦牺,拆包沒什么概念,于是就簡單去了解一下。
3.1什么是拆包粘包枕面?為什么會出現(xiàn)愿卒?
在進行Java NIO學習時渐行,可能會發(fā)現(xiàn):如果客戶端連續(xù)不斷的向服務(wù)端發(fā)送數(shù)據(jù)包時撕氧,服務(wù)端接收的數(shù)據(jù)會出現(xiàn)兩個數(shù)據(jù)包粘在一起的情況。
TCP的首部格式:
- TCP是基于字節(jié)流的精算,雖然應用層和TCP傳輸層之間的數(shù)據(jù)交互是大小不等的數(shù)據(jù)塊枕荞,但是TCP把這些數(shù)據(jù)塊僅僅看成一連串無結(jié)構(gòu)的字節(jié)流柜候,沒有邊界;
- 從TCP的幀結(jié)構(gòu)也可以看出躏精,在TCP的首部沒有表示數(shù)據(jù)長度的字段
基于上面兩點渣刷,在使用TCP傳輸數(shù)據(jù)時,才有粘包或者拆包現(xiàn)象發(fā)生的可能矗烛。
一個數(shù)據(jù)包中包含了發(fā)送端發(fā)送的兩個數(shù)據(jù)包的信息辅柴,這種現(xiàn)象即為粘包
接收端收到了兩個數(shù)據(jù)包,但是這兩個數(shù)據(jù)包要么是不完整的瞭吃,要么就是多出來一塊碌嘀,這種情況即發(fā)生了拆包和粘包
拆包和粘包的問題導致接收端在處理的時候會非常困難(因為無法區(qū)分一個完整的數(shù)據(jù)包)
3.2解決拆包和粘包
分包機制一般有兩個通用的解決方法:
- 1,特殊字符控制
- 2,在包頭首都添加數(shù)據(jù)包的長度
如果使用netty的話,就有專門的編碼器和解碼器解決拆包和粘包問題了歪架。
tips:UDP沒有粘包問題股冗,但是有丟包和亂序。不完整的包是不會有的和蚪,收到的都是完全正確的包止状。傳送的數(shù)據(jù)單位協(xié)議是UDP報文或用戶數(shù)據(jù)報,發(fā)送的時候既不合并攒霹,也不拆分怯疤。
參考資料
- blog.csdn.net/scythe666/a…--->TCP粘包,拆包及解決方法
- www.ideawu.net/blog/archiv…--->關(guān)于TCP粘包和拆包的終極解答
四催束、select集峦、poll、epoll簡單區(qū)別
NIO回顧:
在Linux下它是這樣子實現(xiàn)I/O復用模型的:
調(diào)用select/poll/epoll
其中一個函數(shù),傳入多個文件描述符矫付,如果有一個文件描述符就緒凯沪,則返回,否則阻塞直到超時买优。
這幾個函數(shù)是有些區(qū)別的妨马,可能有的面試官會問到這三個函數(shù)究竟有什么區(qū)別:
區(qū)別如下圖:
兩句話總結(jié):
-
select和poll
都需要輪詢每個文件描述符挺举,epoll
基于事件驅(qū)動,不用輪詢 -
select和poll
每次都需要拷貝文件描述符烘跺,epoll
不用 -
select
最大連接數(shù)受限湘纵,epoll和poll
最大連接數(shù)不受限
tips:epoll在內(nèi)核中的實現(xiàn),用紅黑樹管理事件塊
4.1通俗例子
現(xiàn)在3y在公司里邊實習滤淳,寫完的代碼需要給測試測一遍梧喷。
select/poll
情況:
- 開發(fā)在寫代碼,此時測試挨個問所有開發(fā)者脖咐,你寫好程序了沒有铺敌?要測試嗎?
epoll
情況:
- 開發(fā)寫完代碼了屁擅,告訴測試:“我寫好代碼了偿凭,你去測測,功能是XXX”派歌。于是測試高高興興去找bug了弯囊。
其他通俗描述[1]:
一個酒吧服務(wù)員(一個線程),前面趴了一群醉漢胶果,突然一個吼一聲“倒酒”(事件)匾嘱,你小跑過去給他倒一杯,然后隨他去吧稽物,突然又一個要倒酒奄毡,你又過去倒上折欠,就這樣一個服務(wù)員服務(wù)好多人贝或,有時沒人喝酒,服務(wù)員處于空閑狀態(tài)锐秦,可以干點別的玩玩手機咪奖。至于epoll與select,poll的區(qū)別在于后兩者的場景中醉漢不說話酱床,你要挨個問要不要酒羊赵,沒時間玩手機了。io多路復用大概就是指這幾個醉漢共用一個服務(wù)員扇谣。
來源:
其他通俗描述[2]:
簡單舉個例子(可能也不是很形象)select/poll飯店服務(wù)員(內(nèi)核)告訴飯店老板(用戶程序):”現(xiàn)在有客人結(jié)賬“但是這個服務(wù)員沒人明確告訴老板昧捷,哪幾桌的客人結(jié)帳。老板得自兒一個一個桌子去問:請問是你要結(jié)帳罐寨?epoll飯店服務(wù)員(內(nèi)核)告訴飯店老板(用戶程序):”1,2,5號客人結(jié)賬“老板就可以直接去1,2,5號桌收錢了
來源:
深入了解參考資料:
- www.cnblogs.com/Anker/p/326…--->select靡挥、poll、epoll之間的區(qū)別總結(jié)[整理]
五鸯绿、Java內(nèi)存模型
JVM博文回顧:
之前在寫JVM的時候簸淀,還一度把JVM內(nèi)存結(jié)構(gòu)與Java內(nèi)存模型給搞混了~~~還好有熱心的網(wǎng)友給我指出來。
JVM內(nèi)存結(jié)構(gòu):
Java內(nèi)存模型:
操作變量時的規(guī)則:
- Java內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存
- 線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝
- 線程對變量的所有操作(讀取毒返、賦值等)都必須在工作內(nèi)存中進行租幕,而不能直接讀寫主內(nèi)存中的變量
從工作內(nèi)存同步回主內(nèi)存實現(xiàn)是通過以下的8種操作來完成:
- lock(鎖定):作用于主內(nèi)存的變量,把一個變量標識為一條線程獨占狀態(tài)拧簸。
- unlock(解鎖):作用于主內(nèi)存變量劲绪,把一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定盆赤。
- read(讀戎槭濉):作用于主內(nèi)存變量,把一個變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中弟劲,以便隨后的load動作使用
- load(載入):作用于工作內(nèi)存的變量祷安,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中。
- use(使用):作用于工作內(nèi)存的變量兔乞,把工作內(nèi)存中的一個變量值傳遞給執(zhí)行引擎汇鞭,每當虛擬機遇到一個需要使用變量的值的字節(jié)碼指令時將會執(zhí)行這個操作。
- assign(賦值):作用于工作內(nèi)存的變量庸追,它把一個從執(zhí)行引擎接收到的值賦值給工作內(nèi)存的變量霍骄,每當虛擬機遇到一個給變量賦值的字節(jié)碼指令時執(zhí)行這個操作。
- store(存儲):作用于工作內(nèi)存的變量淡溯,把工作內(nèi)存中的一個變量的值傳送到主內(nèi)存中读整,以便隨后的write的操作。
- write(寫入):作用于主內(nèi)存的變量咱娶,它把store操作從工作內(nèi)存中一個變量的值傳送到主內(nèi)存的變量中米间。
Java內(nèi)存模型是圍繞著在并發(fā)過程中如何處理原子性、可見性和有序性這3個特征來建立的
保證原子性的操作:
read膘侮、load屈糊、assign、use琼了、store和write
- synchronized鎖
保證有序性(重排序?qū)е聼o序)的操作:
- volatile
- synchronized鎖
保證可見性:
- volatile
- synchronized鎖
- final
在上面也說了逻锐,有序性可以通過volatile和synchronized鎖來保證,但我們一般寫程序的時候不會總是關(guān)注代碼的有序性的雕薪。其實昧诱,我們Java內(nèi)部中有一個原則,叫做先行發(fā)生原則(happens-before)
- “先行發(fā)生”(happens-before)原則可以通過:幾條規(guī)則一攬子地解決并發(fā)環(huán)境下兩個操作之間是否可能存在沖突的所有問題
- 有了這些規(guī)則所袁,并且我們的操作是在這些規(guī)則定義的范圍之內(nèi)盏档。我們就可以確保,A操作肯定比B操作先發(fā)生(不會出現(xiàn)重排序的問題)
“先行發(fā)生”(happens-before)原則有下面這么幾條:
- 程序次序規(guī)則(Program Order Rule):在一個線程內(nèi)纲熏,按照程序代碼順序妆丘,書寫在前面的操作先行發(fā)生于書寫在后面的操作锄俄。準確地說,應該是控制流順序而不是程序代碼順序勺拣,因為要考慮分支奶赠、循環(huán)等結(jié)構(gòu)。
- 管程鎖定規(guī)則(Monitor Lock Rule):一個unlock操作先行發(fā)生于后面對同一個鎖的lock操作药有。這里必須強調(diào)的是同一個鎖毅戈,而“后面”是指時間上的先后順序。
- volatile變量規(guī)則(Volatile Variable Rule):對一個volatile變量的寫操作先行發(fā)生于后面對這個變量的讀操作愤惰,這里的“后面”同樣是指時間上的先后順序苇经。線程啟動規(guī)則(Thread Start Rule):Thread對象的start()方法先行發(fā)生于此線程的每一個動作。
- 線程終止規(guī)則(Thread Termination Rule):線程中的所有操作都先行發(fā)生于對此線程的終止檢測宦言,我們可以通過Thread.join()方法結(jié)束扇单、Thread.isAlive()的返回值等手段檢測到線程已經(jīng)終止執(zhí)行。
- 線程中斷規(guī)則(Thread Interruption Rule):對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生奠旺,可以通過Thread.interrupted()方法檢測到是否有中斷發(fā)生蜘澜。
- 對象終結(jié)規(guī)則(Finalizer Rule):一個對象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的finalize()方法的開始。
- 傳遞性(Transitivity):如果操作A先行發(fā)生于操作B响疚,操作B先行發(fā)生于操作C鄙信,那就可以得出操作A先行發(fā)生于操作C的結(jié)論。
參考資料:
- 【深入理解JVM】:Java內(nèi)存模型JMM:blog.csdn.net/u011080472/…
- Java的內(nèi)存模型(1):www.cnblogs.com/jian0110/p/…
六忿晕、最后
本文簡單整理了一下在學習中做的筆記装诡,還有在網(wǎng)上遇到一些比較重要的知識點(面試題)~希望大家看完能有所收益。
參考資料:
- 《深入理解Java虛擬機——JVM高級特性與最佳實踐(第2版)》
如果大家有更好的理解方式或者文章有錯誤的地方還請大家不吝在評論區(qū)留言践盼,大家互相學習交流~~~