【Java】幾道讓你拿offer的知識點

前言

只有光頭才能變強html

以前在刷博客的時候,發現一些寫得比較好的博客都會默默收藏起來。最近在查閱補漏,有的知識點比較重要的,可是在以前的博客中尚未寫到,因而趁着閒整理一下。java

文本的知識點:git

  • Integer常量池
  • TCP拆包粘包
  • select、poll、epoll簡單區別
  • jdk1.6之後對Synchronize鎖優化
  • Java內存模型

本文力求簡單講清每一個知識點,但願你們看完能有所收穫github

1、神奇的Integer

前陣子在羣上看有人在討論關於Integer的true或者false問題,我本覺得我已經懂了這方面的知識點了。但仍是作錯了,後來去請教了一下朋友。朋友又給我發了另外一張圖:面試

後來發現這是出自《深刻理解Java虛擬機——JVM高級特性與最佳實踐(第2版)》中的10.3.2小節中~c#

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類型
  • 注意自動拆箱,自動裝箱問題。

反編譯一下看看:

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,若是尚未試過反編譯的同窗能夠下載來玩玩:

2、Synchronize鎖優化手段有哪些

多線程文章回顧:

以前在寫多線程文章的時候,簡單說了一下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中就已經改成默認開啓了。

參考資料:

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也不作。

參考資料:

3、TCP粘包,拆包

這是在看wangjingxin大佬面經的時候看到的面試題,以前對TCP粘包,拆包沒什麼概念,因而就簡單去了解一下。

3.1什麼是拆包粘包?爲何會出現?

在進行Java NIO學習時,可能會發現:若是客戶端接二連三的向服務端發送數據包時,服務端接收的數據會出現兩個數據包粘在一塊兒的狀況。

TCP的首部格式:

  • TCP是基於字節流的,雖然應用層和TCP傳輸層之間的數據交互是大小不等的數據塊,可是TCP把這些數據塊僅僅當作一連串無結構的字節流,沒有邊界
  • 從TCP的幀結構也能夠看出,在TCP的首部沒有表示數據長度的字段

基於上面兩點,在使用TCP傳輸數據時,纔有粘包或者拆包現象發生的可能。

一個數據包中包含了發送端發送的兩個數據包的信息,這種現象即爲粘包

接收端收到了兩個數據包,可是這兩個數據包要麼是不完整的,要麼就是多出來一塊,這種狀況即發生了拆包和粘包

拆包和粘包的問題致使接收端在處理的時候會很是困難(由於沒法區分一個完整的數據包)

3.2解決拆包和粘包

分包機制通常有兩個通用的解決方法:

  • 1,特殊字符控制
  • 2,在包頭首都添加數據包的長度

若是使用netty的話,就有專門的編碼器和解碼器解決拆包和粘包問題了。

tips:UDP沒有粘包問題,可是有丟包和亂序。不完整的包是不會有的,收到的都是徹底正確的包。傳送的數據單位協議是UDP報文或用戶數據報,發送的時候既不合並,也不拆分。

參考資料

4、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號桌收錢了

來源:

深刻了解參考資料:

5、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的結論。

參考資料:

6、最後

本文簡單整理了一下在學習中作的筆記,還有在網上遇到一些比較重要的知識點(面試題)~但願你們看完能有所收益。

參考資料:

  • 《深刻理解Java虛擬機——JVM高級特性與最佳實踐(第2版)》

若是你們有更好的理解方式或者文章有錯誤的地方還請你們不吝在評論區留言,你們互相學習交流~~~

若是想看更多的原創技術文章,歡迎你們關注個人微信公衆號:Java3y。Java技術羣討論:742919422。公衆號還有海量的視頻資源哦,關注便可免費領取。

可能感興趣的連接:

相關文章
相關標籤/搜索