java經典面試題

java經典面試題

什麼是線程安全?

又是一個理論的問題,各式各樣的答案有很多,我給出一個個人認爲解釋地最好的:如果你的代碼在多線程下執行和在單線程下執行永遠都能獲得一樣的結果,那麼你的代碼就是線程安全的。
線程安全也可以劃分爲幾個級別,java語言中各種擦偶哦共享的數據分爲一下5類:不可變,絕對線程安全,相對線程安全,線程兼容,線程對立。

  1. 不可變:訪問不可變對應,string或者final修飾的對象(不能引用逃逸),永遠都是安全的。
  2. 絕對線程安全:不管運行環境如何,調用者不需要額外的同步措施。比如vector中的每個方法都是synchronized的但是這些方法組合起來用的時候,仍然不能保證線程安全。
  3. 相對線程安全:它能保證對象單獨的操作是線程安全的,在調用的時候不需要額外的同步。但是如果特定順序的連續調用,可能需要額外的手段來保證了。比如剛纔的vector,hashtable都是相對安全的。
  4. 線程兼容:對象本身不是線程安全的,但是可以通過在調用端正確的使用同步手段來保證對象在併發環境的安全使用。
  5. 線程對立:無用是否採用了同步措施,都無法在多線程環境併發的使用代碼。比如Thread的suspend和resume操作,可能產生死鎖風險。
怎麼實現線程安全
  1. 互斥同步
    通過synchronized,Semaphore,Mutex等實現互斥。synchronized會在生成字節碼時,加上monitorenter和monitorexit兩個字節碼指令,這個關鍵字是可重入的,同一個線程可多次通過synchronized獲取對象的鎖,每進入一次,鎖的計數器加1,退出關鍵區時,鎖的計數器減1.當計數器減爲0時,鎖釋放,其他線程可獲取鎖。synchronized
    是非公平鎖。線程一旦因爲synchronized進入阻塞狀態,就需要從用戶態轉換爲內核態。因爲阻塞和喚醒線程需要操作系統幫忙。
    reentrantlock是api層面的鎖,比synchronized提供了一些高級功能:等待可中斷,公平鎖,綁定多個條件。
    性能上,jdk6對synchronized做了很多優化,兩者並未有多大區別,因此性能不是主要的考慮因素。

  2. 非阻塞同步
    互斥同步相當於悲觀鎖,認爲競爭總會發生,所以總是需要加鎖。非阻塞同步使用樂觀鎖,如果沒有競爭,會直接成功,
    如果發生了競爭,則進行嘗試。失敗重視這個操作需要依賴硬件指令集,來保證cas是一個原子操作。如果不能cas是原子操作,則不能保證cas的正確性。cas不能解決aba的問題。

  3. 無同步方案
    因爲有些代碼天生就是線程安全的,這類叫做可重入代碼。這類代碼有這樣的特點:不會訪問共享變量,不會調用非重入方法
    還有一種線程本地存儲方案,threadlocal,變量存儲在線程裏,不存在共享問題。多用空間來解決共享問題。但是也很多場景不適用,因爲有的場景就是要通過共享變量來進行線程間通信的效果。

volatile變量

普通變量在線程間的傳遞需要藉助於主內存來完成。比如線程a修改一個變量的值,然後向主內存同步。線程b在線程a回寫完成之後再從主內存進行讀取,新變量纔會對線程b可見。

volatile變量可以保證可見性,即對volatile變量的所有寫操作都能立刻反應到其他線程中。因爲volatile變量
在每次使用之前都必須從主內存刷新最新的值,執行引擎看不到不一致的情況。

volatile變量的另一個重要作用就是禁止指令重排序,如果指令之間有依賴關係,比如指令2依賴了指令1的運算結果,
重排序後,指令2肯定還是排在指令1後面。對於沒有依賴關係的指令,執行順序可能跟代碼中的順序不一樣,這是機器級別的優化操作。當更改volatile變量的值時,會加入一個內存屏障,防止後面的指令重排序到這個指令前。寫入操作相當於做了一次store和write操作,每次修改後,必須立刻同步回主內存。

java內存模型

java內存模型定義了各個對象變量的訪問規則,不包括局部變量和方法參數。因爲局部變量不會被共享,不存在競爭問題。
java內存模型規定了所有的變量都存儲在主內存,每條線程還有自己的工作內存。線程的工作內存保存了該線程使用到的變量的主內存副本拷貝。線程對變量的所有操作都必須在工作內存中進行,而不能直接操作主內存中的變量。

線程間變量的共享都要通過主內存來完成。理清線程、主內存、工作內存的關係。

對於上面說的線程工作內存有變量的拷貝,並不一定是拷貝整個對象變量,也可能是拷貝對象中的某個字段。

內存間的變量交互操作,load use store write

先行發生原則(happen before原則)

意思就是規定了一些保證可見性的操作。操作A先行於操作b發生,那麼操作A產生的結果一定能夠被b看到。所謂的操作包括了修改變量的值,發送消息等。java內存模型保證了一些天然的先行發生關係:

  • 一個線程內,按照代碼順序,書寫在前面的操作先行發生於在書寫在後面的操作。
  • unlock操作先行發生於同一個鎖的lock操作。
  • 線程的start操作發生在終止操作之前
  • 對象的初始化先行發生於finalize方法被調用前。
  • 等等其他一些操作

先行發生具有傳遞性。
先行發生原則跟代碼的執行時間先後順序沒有必然關係。

使用for(int j = 0; j < size; j ++)這種方式遍歷list要比使用foreach快

因爲foreach底層是使用iterator來做的。每次遍歷前,都要調用一次hasNext()方法。
第一種方式是直接用地址來取元素。

get和post的區別

get參數掛在後面,只能url編碼,可以保留查詢參數,參數長度有限制。
post參數在body裏,支持多種編碼,長度無限制。

底層都是基於tcp沒有本質區別。post請求也可以在鏈接後面掛參數。get也可以有body。
他們的區別只是瀏覽器和http協議以及大家的常識來約書的。
至於網上說的get發一次請求,post發兩次請求,我一直沒有證實。
link

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章