首頁  >  文章  >  Java  >  Java面試題大全 —推薦幾道面試題讓你輕鬆拿下offer

Java面試題大全 —推薦幾道面試題讓你輕鬆拿下offer

php是最好的语言
php是最好的语言原創
2018-08-02 10:54:372243瀏覽

前言

只有光頭才能變強

之前在刷部落格的時候,發現一些寫得比較好的部落格都會默默收藏起來。最近在查閱補漏,有的知識點比較重要的,但是在之前的博客中還沒有寫到,於是趁著閒整理一下(推薦:JAVA面試題大全)。

文字的知識點:

  • Integer常數池

  • TCP拆包黏包

  • select、poll、epoll簡單差別

  • #jdk1.6以後對Synchronize鎖定最佳化

  • ## Java記憶體模型

本文

力求簡單講清每個知識點

,希望大家看完能有所收穫

一、神奇的Integer

前陣子在群組上看有人在討論關於Integer的true或false問題,我以為我已經懂了這方面的知識點了。但還是做錯了,後來去請教了一下朋友。朋友又傳了另一張圖給我:Java面試題大全 —推薦幾道面試題讓你輕鬆拿下offer

後來發現這是出自《深入理解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;
        Long h = 2L;
        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

Java面試題大全 —推薦幾道面試題讓你輕鬆拿下offer

    答案是:
  • #true
  • false

  • ##true

  • true
    • #true

    • ##false
    • #true

    • 簡單解釋一下:

    • #使用
    ==
  • 的情況:

  • 如果比較Integer變量,預設比較的是
      位址值
    • Java的Integer維護了從-128~127

      的快取池
    • 如果比較的某一邊有操作表達式(例如a b),那麼比較的是具體數值

  • #使用

    equals()

    的情況:

Java面試題大全 —推薦幾道面試題讓你輕鬆拿下offer

無論是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

    # ,如果還沒試過反編譯的同學可以下載來玩:
  • https://github.com/java-decompiler/jd-gui/releases
  • #二、Synchronize鎖定優化手段有哪些
  • 多線程文章回顧:

  • ThreadLocal就是這麼簡單

Java面試題大全 —推薦幾道面試題讓你輕鬆拿下offer

#多線程三分鐘就可以入個門了!

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中就已经改为默认开启了。

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

参考资料:

  • https://blog.csdn.net/chenssy/article/details/54883355

三、TCP粘包,拆包

这是在看wangjingxin大佬面经的时候看到的面试题,之前对TCP粘包,拆包没什么概念,于是就简单去了解一下。

3.1什么是拆包粘包?为什么会出现?

在进行Java NIO学习时,可能会发现:如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况。

TCP的首部格式:

Java面試題大全 —推薦幾道面試題讓你輕鬆拿下offer

  • TCP是基于字节流的,虽然应用层和TCP传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界

  • 从TCP的帧结构也可以看出,在TCP的首部没有表示数据长度的字段

基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能。

一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包

Java面試題大全 —推薦幾道面試題讓你輕鬆拿下offer

接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包

Java面試題大全 —推薦幾道面試題讓你輕鬆拿下offer

拆包和粘包的问题导致接收端在处理的时候会非常困难(因为无法区分一个完整的数据包)

3.2解決拆包和黏包

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

  • 1,特殊字符控制

  • 2,在包頭首都添加資料包的長度

如果使用netty的話,就有專門的編碼器和解碼器解決拆包和黏包問題了。

tips:UDP沒有黏包問題,但是有丟包和亂序。不完整的包是不會有的,收到的都是完全正確的包。傳送的資料單位協定是UDP封包或用戶資料報,發送的時候既不合併,也不拆分。

四、select、poll、epoll簡單區別

NIO回顧:

  • JDK10都發布了,nio你了解多少?

在Linux下它是這樣子實作I/O復用模型的:

呼叫select/poll/epoll其中一個函數,傳入多個檔案描述符,如果有一個檔案描述符就緒,則返回,否則阻塞直到逾時。

這幾個函數是有些差別的,可能有的面試官會問到這三個函數究竟有什麼差別:

區別如下圖:

Java面試題大全 —推薦幾道面試題讓你輕鬆拿下offer

兩句話總結:

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

五、Java記憶體模型

JVM博文回顧:

  • JVM如何從入門到放棄的?

之前在寫JVM的時候,還一度把JVM記憶體結構與Java記憶體模型給搞混了~~~還好有熱心的網友給我指出來。

JVM記憶體結構:

Java面試題大全 —推薦幾道面試題讓你輕鬆拿下offer

Java記憶體模型:

Java面試題大全 —推薦幾道面試題讓你輕鬆拿下offer

##操作變數時的規則:

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

相關文章:


分享java中面試題總結

10個經典的 Java main 方法面試題

以上是Java面試題大全 —推薦幾道面試題讓你輕鬆拿下offer的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn