前言 只有光頭才能變強 之前在刷博客的時候,發現一些寫得比較好的博客都會默默收藏起來。最近在查閱補漏,有的知識點比較重要的,但是在之前的博客中還沒有寫到,於是趁著閑整理一下。 文本的知識點: Integer常量池 TCP拆包粘包 簡單區別 jdk1.6以後對Synchronize鎖優化 Java記憶體 ...
前言
只有光頭才能變強
之前在刷博客的時候,發現一些寫得比較好的博客都會默默收藏起來。最近在查閱補漏,有的知識點比較重要的,但是在之前的博客中還沒有寫到,於是趁著閑整理一下。
文本的知識點:
- Integer常量池
- TCP拆包粘包
select、poll、epoll
簡單區別- jdk1.6以後對Synchronize鎖優化
- Java記憶體模型
本文力求簡單講清每個知識點,希望大家看完能有所收穫
一、神奇的Integer
前陣子在群上看有人在討論關於Integer的true或者false問題,我本以為我已經懂了這方面的知識點了。但還是做錯了,後來去請教了一下朋友。朋友又給我發了另一張圖:
後來發現這是出自《深入理解Java虛擬機——JVM高級特性與最佳實踐(第2版)》中的10.3.2小節中~
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解題思路
在解這道題之前,相信很多人都已經知道了,在Java中會有一個Integer緩存池,緩存的大小是:-128~127
答案是:
- true
- false
- true
- true
- true
- false
- true
簡單解釋一下:
- 使用
==
的情況:- 如果比較Integer變數,預設比較的是地址值。
- Java的Integer維護了從
-128~127
的緩存池 - 如果比較的某一邊有操作表達式(例如a+b),那麼比較的是具體數值
- 使用
equals()
的情況:- 無論是Integer還是Long中的
equals()
預設比較的是數值。 - Long的
equals()
方法,JDK的預設實現:會判斷是否是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數值表達式,比較的是數值
System.out.println(localInteger3.intValue() == localInteger1.intValue() + localInteger2.intValue());
// equals比較的是數值
System.out.println(localInteger3.equals(Integer.valueOf(localInteger1.intValue() + localInteger2.intValue())));
// 存在a+b數值表達式,比較的是數值
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鎖優化手段有哪些
多線程文章回顧:
- ThreadLocal就是這麼簡單
- 多線程三分鐘就可以入個門了!
- Thread源碼剖析
- 多線程基礎必要知識點!看了學習多線程事半功倍
- Java鎖機制瞭解一下
- AQS簡簡單單過一遍
- Lock鎖子類瞭解一下
- 線程池你真不來瞭解一下嗎?
- 多線程之死鎖就是這麼簡單
- Java多線程打輔助的三個小伙子
之前在寫多線程文章的時候,簡單說了一下synchronized鎖在jdk1.6以後會有各種的優化:適應自旋鎖,鎖消除,鎖粗化,輕量級鎖,偏向鎖。
本以為這些優化是非常難以理解的東西,其實不然~~~簡單瞭解一下還是很好理解的。
2.1適應自旋鎖
鎖競爭是kernal mode下的,會經過user mode(用戶態)到kernal mode(內核態) 的切換,是比較花時間的。
自旋鎖出現的原因是人們發現大多數時候鎖的占用只會持續很短的時間,甚至低於切換到kernal mode所花的時間,所以在進入kernal mode前讓線程等待有限的時間,如果在此時間內能夠獲取到鎖就避免了很多無謂的時間,若不能則再進入kernal mode競爭鎖。
在JDK 1.6中引入了自適應的自旋鎖,說明自旋的時間不固定,要不要自旋變得越來越聰明。
自旋鎖在JDK1.4.2中就已經引入,只不過預設是關閉的,可以使用-XX:+UseSpinning
參數來開啟,在JDK1.6中就已經改為預設開啟了。
參考資料:
- 自旋鎖和使線程休眠的非自旋鎖各有什麼適用場景?https://www.zhihu.com/question/38857029/answer/78480263
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如果發現vector變數僅僅在vectorTest()
方法中使用,那該vector是線程安全的。JVM會把vector內部加的鎖去除,這個優化就叫做:鎖消除。
2.3鎖粗化
預設情況下,總是推薦將同步塊的作用範圍限制得儘量小。
但是如果一系列的連續操作都對同一個對象反覆加鎖和解鎖,甚至加鎖操作是出現在迴圈體中的,頻繁地進行互斥同步操作也會導致不必要的性能損耗。
JVM會將加鎖的範圍擴展(粗化),這就叫做鎖粗化。
2.4輕量級鎖
輕量級鎖能提升程式同步性能的依據是“對於絕大部分的鎖,在整個同步周期內都是不存在競爭的”,這是一個經驗數據。
- 如果沒有競爭,輕量級鎖使用CAS操作避免了使用互斥量的開銷
- 但如果存在鎖競爭,除了互斥量的開銷外,還額外發生了CAS操作,因此在有競爭的情況下,輕量級鎖會比傳統的重量級鎖更慢。
簡單來說:如果發現同步周期內都是不存在競爭,JVM會使用CAS操作來替代操作系統互斥量。這個優化就被叫做輕量級鎖。
2.5偏向鎖
偏向鎖就是在無競爭的情況下把整個同步都消除掉,連CAS操作都不做了!
偏向鎖可以提高帶有同步但無競爭的程式性能。它同樣是一個帶有效益權衡(Trade Off)性質的優化,也就是說,它並不一定總是對程式運行有利,如果程式中大多數的鎖總是被多個不同的線程訪問,那偏向模式就是多餘的。在具體問題具體分析的前提下,有時候使用參數
-XX:-UseBiasedLocking
來禁止偏向鎖優化反而可以提升性能。
2.6簡單總結各種鎖優化
- 自適應偏向鎖:自旋時間不固定
- 鎖消除:如果發現代碼是線程安全的,將鎖去掉
- 鎖粗化:加鎖範圍過小(重覆加鎖),將加鎖的範圍擴展
- 輕量級鎖:在無競爭的情況下使用CAS操作去消除同步使用的互斥量
- 偏向鎖:在無競爭環境下,把整個同步都消除,CAS也不做。
參考資料:
三、TCP粘包,拆包
這是在看wangjingxin大佬面經的時候看到的面試題,之前對TCP粘包,拆包沒什麼概念,於是就簡單去瞭解一下。
3.1什麼是拆包粘包?為什麼會出現?
在進行Java NIO學習時,可能會發現:如果客戶端連續不斷的向服務端發送數據包時,服務端接收的數據會出現兩個數據包粘在一起的情況。
TCP的首部格式:
- TCP是基於位元組流的,雖然應用層和TCP傳輸層之間的數據交互是大小不等的數據塊,但是TCP把這些數據塊僅僅看成一連串無結構的位元組流,沒有邊界;
- 從TCP的幀結構也可以看出,在TCP的首部沒有表示數據長度的欄位
基於上面兩點,在使用TCP傳輸數據時,才有粘包或者拆包現象發生的可能。
一個數據包中包含了發送端發送的兩個數據包的信息,這種現象即為粘包
接收端收到了兩個數據包,但是這兩個數據包要麼是不完整的,要麼就是多出來一塊,這種情況即發生了拆包和粘包
拆包和粘包的問題導致接收端在處理的時候會非常困難(因為無法區分一個完整的數據包)
3.2解決拆包和粘包
分包機制一般有兩個通用的解決方法:
- 1,特殊字元控制
- 2,在包頭首都添加數據包的長度
如果使用netty的話,就有專門的編碼器和解碼器解決拆包和粘包問題了。
tips:UDP沒有粘包問題,但是有丟包和亂序。不完整的包是不會有的,收到的都是完全正確的包。傳送的數據單位協議是UDP報文或用戶數據報,發送的時候既不合併,也不拆分。
參考資料
- https://blog.csdn.net/scythe666/article/details/51996268--->TCP粘包,拆包及解決方法
- http://www.ideawu.net/blog/archives/993.html--->關於TCP粘包和拆包的終極解答
四、select、poll、epoll簡單區別
NIO回顧:
在Linux下它是這樣子實現I/O復用模型的:
調用select/poll/epoll
其中一個函數,傳入多個文件描述符,如果有一個文件描述符就緒,則返回,否則阻塞直到超時。
這幾個函數是有些區別的,可能有的面試官會問到這三個函數究竟有什麼區別:
區別如下圖:
兩句話總結:
select和poll
都需要輪詢每個文件描述符,epoll
基於事件驅動,不用輪詢select和poll
每次都需要拷貝文件描述符,epoll
不用select
最大連接數受限,epoll和poll
最大連接數不受限
tips:epoll在內核中的實現,用紅黑樹管理事件塊
4.1通俗例子
現在3y在公司裡邊實習,寫完的代碼需要給測試測一遍。
select/poll
情況:
- 開發在寫代碼,此時測試挨個問所有開發者,你寫好程式了沒有?要測試嗎?
epoll
情況:
- 開發寫完代碼了,告訴測試:“我寫好代碼了,你去測測,功能是XXX”。於是測試高高興興去找bug了。
其他通俗描述[1]:
一個酒吧服務員(一個線程),前面趴了一群醉漢,突然一個吼一聲“倒酒”(事件),你小跑過去給他倒一杯,然後隨他去吧,突然又一個要倒酒,你又過去倒上,就這樣一個服務員服務好多人,有時沒人喝酒,服務員處於空閑狀態,可以乾點別的玩玩手機。至於epoll與select,poll的區別在於後兩者的場景中醉漢不說話,你要挨個問要不要酒,沒時間玩手機了。io多路復用大概就是指這幾個醉漢共用一個服務員。
來源:
其他通俗描述[2]:
簡單舉個例子(可能也不是很形象)select/poll飯店服務員(內核)告訴飯店老闆(用戶程式):”現在有客人結賬“但是這個服務員沒人明確告訴老闆,哪幾桌的客人結帳。老闆得自兒一個一個桌子去問:請問是你要結帳?epoll飯店服務員(內核)告訴飯店老闆(用戶程式):”1,2,5號客人結賬“老闆就可以直接去1,2,5號桌收錢了
來源:
深入瞭解參考資料:
- https://www.cnblogs.com/Anker/p/3265058.html--->select、poll、epoll之間的區別總結[整理]
五、Java記憶體模型
JVM博文回顧:
之前在寫JVM的時候,還一度把JVM記憶體結構與Java記憶體模型給搞混了~~~還好有熱心的網友給我指出來。
JVM記憶體結構:
Java記憶體模型:
操作變數時的規則:
- Java記憶體模型規定了所有的變數都存儲在主記憶體
- 線程的工作記憶體中保存了被該線程使用到的變數的主記憶體副本拷貝
- 線程對變數的所有操作(讀取、賦值等)都必須在工作記憶體中進行,而不能直接讀寫主記憶體中的變數
從工作記憶體同步回主記憶體實現是通過以下的8種操作來完成:
- lock(鎖定):作用於主記憶體的變數,把一個變數標識為一條線程獨占狀態。
- unlock(解鎖):作用於主記憶體變數,把一個處於鎖定狀態的變數釋放出來,釋放後的變數才可以被其他線程鎖定。
- read(讀取):作用於主記憶體變數,把一個變數值從主記憶體傳輸到線程的工作記憶體中,以便隨後的load動作使用
- load(載入):作用於工作記憶體的變數,它把read操作從主記憶體中得到的變數值放入工作記憶體的變數副本中。
- use(使用):作用於工作記憶體的變數,把工作記憶體中的一個變數值傳遞給執行引擎,每當虛擬機遇到一個需要使用變數的值的位元組碼指令時將會執行這個操作。
- assign(賦值):作用於工作記憶體的變數,它把一個從執行引擎接收到的值賦值給工作記憶體的變數,每當虛擬機遇到一個給變數賦值的位元組碼指令時執行這個操作。
- store(存儲):作用於工作記憶體的變數,把工作記憶體中的一個變數的值傳送到主記憶體中,以便隨後的write的操作。
- write(寫入):作用於主記憶體的變數,它把store操作從工作記憶體中一個變數的值傳送到主記憶體的變數中。
Java記憶體模型是圍繞著在併發過程中如何處理原子性、可見性和有序性這3個特征來建立的
保證原子性的操作:
read、load、assign、use、store和write
- synchronized鎖
保證有序性(重排序導致無序)的操作:
- volatile
- synchronized鎖
保證可見性:
- volatile
- synchronized鎖
- final
在上面也說了,有序性可以通過volatile和synchronized鎖來保證,但我們一般寫程式的時候不會總是關註代碼的有序性的。其實,我們Java內部中有一個原則,叫做先行發生原則(happens-before)
- “先行發生”(happens-before)原則可以通過:幾條規則一攬子地解決併發環境下兩個操作之間是否可能存在衝突的所有問題
- 有了這些規則,並且我們的操作是在這些規則定義的範圍之內。我們就可以確保,A操作肯定比B操作先發生(不會出現重排序的問題)
“先行發生”(happens-before)原則有下麵這麼幾條:
- 程式次序規則(Program Order Rule):在一個線程內,按照程式代碼順序,書寫在前面的操作先行發生於書寫在後面的操作。準確地說,應該是控制流順序而不是程式代碼順序,因為要考慮分支、迴圈等結構。
- 管程鎖定規則(Monitor Lock Rule):一個unlock操作先行發生於後面對同一個鎖的lock操作。這裡必須強調的是同一個鎖,而“後面”是指時間上的先後順序。
- volatile變數規則(Volatile Variable Rule):對一個volatile變數的寫操作先行發生於後面對這個變數的讀操作,這裡的“後面”同樣是指時間上的先後順序。線程啟動規則(Thread Start Rule):Thread對象的start()方法先行發生於此線程的每一個動作。
- 線程終止規則(Thread Termination Rule):線程中的所有操作都先行發生於對此線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值等手段檢測到線程已經終止執行。
- 線程中斷規則(Thread Interruption Rule):對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生,可以通過Thread.interrupted()方法檢測到是否有中斷發生。
- 對象終結規則(Finalizer Rule):一個對象的初始化完成(構造函數執行結束)先行發生於它的finalize()方法的開始。
- 傳遞性(Transitivity):如果操作A先行發生於操作B,操作B先行發生於操作C,那就可以得出操作A先行發生於操作C的結論。
參考資料:
- 【深入理解JVM】:Java記憶體模型JMM:https://blog.csdn.net/u011080472/article/details/51337422
- Java的記憶體模型(1):https://www.cnblogs.com/jian0110/p/9351281.html
六、最後
本文簡單整理了一下在學習中做的筆記,還有在網上遇到一些比較重要的知識點(面試題)~希望大家看完能有所收益。
參考資料:
- 《深入理解Java虛擬機——JVM高級特性與最佳實踐(第2版)》
如果大家有更好的理解方式或者文章有錯誤的地方還請大家不吝在評論區留言,大家互相學習交流~~~
如果想看更多的原創技術文章,歡迎大家關註我的微信公眾號:Java3y。Java技術群討論:742919422。公眾號還有海量的視頻資源哦,關註即可免費領取。
可能感興趣的鏈接:
- 文章的目錄導航(微信公眾號端):https://zhongfucheng.bitcron.com/post/shou-ji/wen-zhang-dao-hang
- 文章的目錄導航(PC端):http://www.zhongfucheng.bitcron.com/post/shou-ji/pcduan-wen-zhang-dao-hang
- 海量精美腦圖:http://www.zhongfucheng.bitcron.com/post/shou-ji/nao-tu-da-quan