哪些常常在java面試官嘴上的面試題

閱讀文本大概須要3分鐘。javascript

Java 基礎


1. JDK 和 JRE 有什麼區別?php


  • JDK:Java Development Kit 的簡稱,java 開發工具包,提供了 java 的開發環境和運行環境。java

  • JRE:Java Runtime Environment 的簡稱,java 運行環境,爲 java 的運行提供了所需環境。程序員


具體來講 JDK 其實包含了 JRE,同時還包含了編譯 java 源碼的編譯器 javac,還包含了不少 java 程序調試和分析的工具。簡單來講:若是你須要運行 java 程序,只需安裝 JRE 就能夠了,若是你須要編寫 java 程序,須要安裝 JDK。web


2. == 和 equals 的區別是什麼?正則表達式


== 解讀算法


對於基本類型和引用類型 == 的做用效果是不一樣的,以下所示:spring


  • 基本類型:比較的是值是否相同;sql

  • 引用類型:比較的是引用是否相同;typescript


代碼示例:


String x = "string";String y = "string";String z = new String("string");System.out.println(x==y); // trueSystem.out.println(x==z); // falseSystem.out.println(x.equals(y)); // trueSystem.out.println(x.equals(z)); // true


代碼解讀:由於 x 和 y 指向的是同一個引用,因此 == 也是 true,而 new String()方法則重寫開闢了內存空間,因此 == 結果爲 false,而 equals 比較的一直是值,因此結果都爲 true。


equals 解讀


equals 本質上就是 ==,只不過 String 和 Integer 等重寫了 equals 方法,把它變成了值比較。看下面的代碼就明白了。


首先來看默認狀況下 equals 比較一個有相同值的對象,代碼以下:


class Cat {public Cat(String name) {this.name = name; }
private String name;
public String getName() {return name; }
public void setName(String name) {this.name = name; }}
Cat c1 = new Cat("王磊");Cat c2 = new Cat("王磊");System.out.println(c1.equals(c2)); // false


輸出結果出乎咱們的意料,居然是 false?這是怎麼回事,看了 equals 源碼就知道了,源碼以下:


public boolean equals(Object obj) {return (this == obj);}


原來 equals 本質上就是 ==。

那問題來了,兩個相同值的 String 對象,爲何返回的是 true?代碼以下:

String s1 = new String("老王");String s2 = new String("老王");System.out.println(s1.equals(s2)); // true


一樣的,當咱們進入 String 的 equals 方法,找到了答案,代碼以下:


public boolean equals(Object anObject) {if (this == anObject) {return true; }if (anObject instanceof String) { String anotherString = (String)anObject;int n = value.length;if (n == anotherString.value.length) {char v1[] = value;char v2[] = anotherString.value;int i = 0;while (n-- != 0) {if (v1[i] != v2[i])return false; i++; }return true; } }return false;}


原來是 String 重寫了 Object 的 equals 方法,把引用比較改爲了值比較。


總結 :== 對於基本類型來講是值比較,對於引用類型來講是比較的是引用;而 equals 默認狀況下是引用比較,只是不少類從新了 equals 方法,好比 String、Integer 等把它變成了值比較,因此通常狀況下 equals 比較的是值是否相等。


3. 兩個對象的 hashCode()相同,則 equals()也必定爲 true,對嗎?


不對,兩個對象的 hashCode()相同,equals()不必定 true。


代碼示例:


String str1 = "通話";String str2 = "重地";System.out.println(String.format("str1:%d | str2:%d", str1.hashCode(),str2.hashCode()));System.out.println(str1.equals(str2));


執行的結果:


str1:1179395 | str2:1179395


false


代碼解讀:很顯然「通話」和「重地」的 hashCode() 相同,然而 equals() 則爲 false,由於在散列表中,hashCode()相等即兩個鍵值對的哈希值相等,然而哈希值相等,並不必定能得出鍵值對相等。


4. final 在 java 中有什麼做用?


  • final 修飾的類叫最終類,該類不能被繼承。

  • final 修飾的方法不能被重寫。

  • final 修飾的變量叫常量,常量必須初始化,初始化以後值就不能被修改。


5. java 中的 Math.round(-1.5) 等於多少?


等於 -1,由於在數軸上取值時,中間值(0.5)向右取整,因此正 0.5 是往上取整,負 0.5 是直接捨棄。


6. String 屬於基礎的數據類型嗎?


String 不屬於基礎類型,基礎類型有 8 種:byte、boolean、char、short、int、float、long、double,而 String 屬於對象。


7. java 中操做字符串都有哪些類?它們之間有什麼區別?


操做字符串的類有:String、StringBuffer、StringBuilder。


String 和 StringBuffer、StringBuilder 的區別在於 String 聲明的是不可變的對象,每次操做都會生成新的 String 對象,而後將指針指向新的 String 對象,而 StringBuffer、StringBuilder 能夠在原有對象的基礎上進行操做,因此在常常改變字符串內容的狀況下最好不要使用 String。


StringBuffer 和 StringBuilder 最大的區別在於,StringBuffer 是線程安全的,而 StringBuilder 是非線程安全的,但 StringBuilder 的性能卻高於 StringBuffer,因此在單線程環境下推薦使用 StringBuilder,多線程環境下推薦使用 StringBuffer。


8. String str="i"與 String str=new String("i")同樣嗎?


不同,由於內存的分配方式不同。String str="i"的方式,java 虛擬機會將其分配到常量池中;而 String str=new String("i") 則會被分到堆內存中。


9. 如何將字符串反轉?


使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。


示例代碼:


// StringBuffer reverseStringBuffer stringBuffer = new StringBuffer();stringBuffer.append("abcdefg");System.out.println(stringBuffer.reverse()); // gfedcba// StringBuilder reverseStringBuilder stringBuilder = new StringBuilder();stringBuilder.append("abcdefg");System.out.println(stringBuilder.reverse()); // gfedcba


10. String 類的經常使用方法都有那些?


  • indexOf():返回指定字符的索引。

  • charAt():返回指定索引處的字符。

  • replace():字符串替換。

  • trim():去除字符串兩端空白。

  • split():分割字符串,返回一個分割後的字符串數組。

  • getBytes():返回字符串的 byte 類型數組。

  • length():返回字符串長度。

  • toLowerCase():將字符串轉成小寫字母。

  • toUpperCase():將字符串轉成大寫字符。

  • substring():截取字符串。

  • equals():字符串比較。


11. 抽象類必需要有抽象方法嗎?


不須要,抽象類不必定非要有抽象方法。


示例代碼:


abstract class Cat {public static void sayHi() { System.out.println("hi~"); }}


上面代碼,抽象類並無抽象方法但徹底能夠正常運行。


12. 普通類和抽象類有哪些區別?


  • 普通類不能包含抽象方法,抽象類能夠包含抽象方法。

  • 抽象類不能直接實例化,普通類能夠直接實例化。


13. 抽象類能使用 final 修飾嗎?


不能,定義抽象類就是讓其餘類繼承的,若是定義爲 final 該類就不能被繼承,這樣彼此就會產生矛盾,因此 final 不能修飾抽象類,以下圖所示,編輯器也會提示錯誤信息:



14. 接口和抽象類有什麼區別?


  • 實現:抽象類的子類使用 extends 來繼承;接口必須使用 implements 來實現接口。

  • 構造函數:抽象類能夠有構造函數;接口不能有。

  • main 方法:抽象類能夠有 main 方法,而且咱們能運行它;接口不能有 main 方法。

  • 實現數量:類能夠實現不少個接口;可是隻能繼承一個抽象類。

  • 訪問修飾符:接口中的方法默認使用 public 修飾;抽象類中的方法能夠是任意訪問修飾符。


15. java 中 IO 流分爲幾種?


按功能來分:輸入流(input)、輸出流(output)。


按類型來分:字節流和字符流。


字節流和字符流的區別是:字節流按 8 位傳輸以字節爲單位輸入輸出數據,字符流按 16 位傳輸以字符爲單位輸入輸出數據。


16. BIO、NIO、AIO 有什麼區別?


  • BIO:Block IO 同步阻塞式 IO,就是咱們日常使用的傳統 IO,它的特色是模式簡單使用方便,併發處理能力低。

  • NIO:New IO 同步非阻塞 IO,是傳統 IO 的升級,客戶端和服務器端經過 Channel(通道)通信,實現了多路複用。

  • AIO:Asynchronous IO 是 NIO 的升級,也叫 NIO2,實現了異步非堵塞 IO ,異步 IO 的操做基於事件和回調機制。


17. Files的經常使用方法都有哪些?


  • Files.exists():檢測文件路徑是否存在。

  • Files.createFile():建立文件。

  • Files.createDirectory():建立文件夾。

  • Files.delete():刪除一個文件或目錄。

  • Files.copy():複製文件。

  • Files.move():移動文件。

  • Files.size():查看文件個數。

  • Files.read():讀取文件。

  • Files.write():寫入文件。

18. java 容器都有哪些?


經常使用容器的圖錄:

19. Collection 和 Collections 有什麼區別?
  • java.util.Collection 是一個集合接口(集合類的一個頂級接口)。它提供了對集合對象進行基本操做的通用接口方法。Collection接口在Java 類庫中有不少具體的實現。Collection接口的意義是爲各類具體的集合提供了最大化的統一操做方式,其直接繼承接口有List與Set。

  • Collections則是集合類的一個工具類/幫助類,其中提供了一系列靜態方法,用於對集合中元素進行排序、搜索以及線程安全等各類操做。


20. List、Set、Map 之間的區別是什麼?



21. HashMap 和 Hashtable 有什麼區別?


  • hashMap去掉了HashTable 的contains方法,可是加上了containsValue()和containsKey()方法。

  • hashTable同步的,而HashMap是非同步的,效率上逼hashTable要高。

  • hashMap容許空鍵值,而hashTable不容許。


22. 如何決定使用 HashMap 仍是 TreeMap?


對於在Map中插入、刪除和定位元素這類操做,HashMap是最好的選擇。然而,假如你須要對一個有序的key集合進行遍歷,TreeMap是更好的選擇。基於你的collection的大小,也許向HashMap中添加元素會更快,將map換爲TreeMap進行有序key的遍歷。


23. 說一下 HashMap 的實現原理?


HashMap概述: HashMap是基於哈希表的Map接口的非同步實現。此實現提供全部可選的映射操做,並容許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變。 

HashMap的數據結構: 在java編程語言中,最基本的結構就是兩種,一個是數組,另一個是模擬指針(引用),全部的數據結構均可以用這兩個基本結構來構造的,HashMap也不例外。HashMap其實是一個「鏈表散列」的數據結構,即數組和鏈表的結合體。

當咱們往Hashmap中put元素時,首先根據key的hashcode從新計算hash值,根絕hash值獲得這個元素在數組中的位置(下標),若是該數組在該位置上已經存放了其餘元素,那麼在這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最早加入的放入鏈尾.若是數組中該位置沒有元素,就直接將該元素放到數組的該位置上。

須要注意Jdk 1.8中對HashMap的實現作了優化,當鏈表中的節點數據超過八個以後,該鏈表會轉爲紅黑樹來提升查詢效率,從原來的O(n)到O(logn)

24. 說一下 HashSet 的實現原理?


  • HashSet底層由HashMap實現

  • HashSet的值存放於HashMap的key上

  • HashMap的value統一爲PRESENT


25. ArrayList 和 LinkedList 的區別是什麼?


最明顯的區別是 ArrrayList底層的數據結構是數組,支持隨機訪問,而 LinkedList 的底層數據結構是雙向循環鏈表,不支持隨機訪問。使用下標訪問一個元素,ArrayList 的時間複雜度是 O(1),而 LinkedList 是 O(n)。

26. 如何實現數組和 List 之間的轉換?


  • List轉換成爲數組:調用ArrayList的toArray方法。

  • 數組轉換成爲List:調用Arrays的asList方法。

27. ArrayList 和 Vector 的區別是什麼?


  • Vector是同步的,而ArrayList不是。然而,若是你尋求在迭代的時候對列表進行改變,你應該使用CopyOnWriteArrayList。 

  • ArrayList比Vector快,它由於有同步,不會過載。 

  • ArrayList更加通用,由於咱們可使用Collections工具類輕易地獲取同步列表和只讀列表。


28. Array 和 ArrayList 有何區別?


  • Array能夠容納基本類型和對象,而ArrayList只能容納對象。 

  • Array是指定大小的,而ArrayList大小是固定的。 

  • Array沒有提供ArrayList那麼多功能,好比addAll、removeAll和iterator等。


29. 在 Queue 中 poll()和 remove()有什麼區別?


poll() 和 remove() 都是從隊列中取出一個元素,可是 poll() 在獲取元素失敗的時候會返回空,可是 remove() 失敗的時候會拋出異常。

30. 哪些集合類是線程安全的?


  • vector:就比arraylist多了個同步化機制(線程安全),由於效率較低,如今已經不太建議使用。在web應用中,特別是前臺頁面,每每效率(頁面響應速度)是優先考慮的。

  • statck:堆棧類,先進後出。

  • hashtable:就比hashmap多了個線程安全。

  • enumeration:枚舉,至關於迭代器。


31. 迭代器 Iterator 是什麼?


迭代器是一種設計模式,它是一個對象,它能夠遍歷並選擇序列中的對象,而開發人員不須要了解該序列的底層結構。迭代器一般被稱爲「輕量級」對象,由於建立它的代價小。


32. Iterator 怎麼使用?有什麼特色?


Java中的Iterator功能比較簡單,而且只能單向移動:

  

(1) 使用方法iterator()要求容器返回一個Iterator。第一次調用Iterator的next()方法時,它返回序列的第一個元素。注意:iterator()方法是java.lang.Iterable接口,被Collection繼承。

  

(2) 使用next()得到序列中的下一個元素。

  

(3) 使用hasNext()檢查序列中是否還有元素。

  

(4) 使用remove()將迭代器新返回的元素刪除。

  

Iterator是Java迭代器最簡單的實現,爲List設計的ListIterator具備更多的功能,它能夠從兩個方向遍歷List,也能夠從List中插入和刪除元素。

33. Iterator 和 ListIterator 有什麼區別?


  • Iterator可用來遍歷Set和List集合,可是ListIterator只能用來遍歷List。 

  • Iterator對集合只能是前向遍歷,ListIterator既能夠前向也能夠後向。 

  • ListIterator實現了Iterator接口,幷包含其餘的功能,好比:增長元素,替換元素,獲取前一個和後一個元素的索引,等等。

多線程


35. 並行和併發有什麼區別?


  • 並行是指兩個或者多個事件在同一時刻發生;而併發是指兩個或多個事件在同一時間間隔發生。

  • 並行是在不一樣實體上的多個事件,併發是在同一實體上的多個事件。

  • 在一臺處理器上「同時」處理多個任務,在多臺處理器上同時處理多個任務。如hadoop分佈式集羣。


因此併發編程的目標是充分的利用處理器的每個核,以達到最高的處理性能。


36. 線程和進程的區別?


簡而言之,進程是程序運行和資源分配的基本單位,一個程序至少有一個進程,一個進程至少有一個線程。進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存資源,減小切換次數,從而效率更高。線程是進程的一個實體,是cpu調度和分派的基本單位,是比程序更小的能獨立運行的基本單位。同一進程中的多個線程之間能夠併發執行。


37. 守護線程是什麼?


守護線程(即daemon thread),是個服務線程,準確地來講就是服務其餘的線程。


38. 建立線程有哪幾種方式?


①. 繼承Thread類建立線程類


  • 定義Thread類的子類,並重寫該類的run方法,該run方法的方法體就表明了線程要完成的任務。所以把run()方法稱爲執行體。

  • 建立Thread子類的實例,即建立了線程對象。

  • 調用線程對象的start()方法來啓動該線程。


②. 經過Runnable接口建立線程類


  • 定義runnable接口的實現類,並重寫該接口的run()方法,該run()方法的方法體一樣是該線程的線程執行體。

  • 建立 Runnable實現類的實例,並依此實例做爲Thread的target來建立Thread對象,該Thread對象纔是真正的線程對象。

  • 調用線程對象的start()方法來啓動該線程。


③. 經過Callable和Future建立線程


  • 建立Callable接口的實現類,並實現call()方法,該call()方法將做爲線程執行體,而且有返回值。

  • 建立Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。

  • 使用FutureTask對象做爲Thread對象的target建立並啓動新線程。

  • 調用FutureTask對象的get()方法來得到子線程執行結束後的返回值。


39. 說一下 runnable 和 callable 有什麼區別?


有點深的問題了,也看出一個Java程序員學習知識的廣度。


  • Runnable接口中的run()方法的返回值是void,它作的事情只是純粹地去執行run()方法中的代碼而已;

  • Callable接口中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合能夠用來獲取異步執行的結果。


40. 線程有哪些狀態?


線程一般都有五種狀態,建立、就緒、運行、阻塞和死亡。


  • 建立狀態。在生成線程對象,並無調用該對象的start方法,這是線程處於建立狀態。

  • 就緒狀態。當調用了線程對象的start方法以後,該線程就進入了就緒狀態,可是此時線程調度程序尚未把該線程設置爲當前線程,此時處於就緒狀態。在線程運行以後,從等待或者睡眠中回來以後,也會處於就緒狀態。

  • 運行狀態。線程調度程序將處於就緒狀態的線程設置爲當前線程,此時線程就進入了運行狀態,開始運行run函數當中的代碼。

  • 阻塞狀態。線程正在運行的時候,被暫停,一般是爲了等待某個時間的發生(好比說某項資源就緒)以後再繼續運行。sleep,suspend,wait等方法均可以致使線程阻塞。

  • 死亡狀態。若是一個線程的run方法執行結束或者調用stop方法後,該線程就會死亡。對於已經死亡的線程,沒法再使用start方法令其進入就緒   


41. sleep() 和 wait() 有什麼區別?


sleep():方法是線程類(Thread)的靜態方法,讓調用線程進入睡眠狀態,讓出執行機會給其餘線程,等到休眠時間結束後,線程進入就緒狀態和其餘線程一塊兒競爭cpu的執行時間。由於sleep() 是static靜態的方法,他不能改變對象的機鎖,當一個synchronized塊中調用了sleep() 方法,線程雖然進入休眠,可是對象的機鎖沒有被釋放,其餘線程依然沒法訪問這個對象。


wait():wait()是Object類的方法,當一個線程執行到wait方法時,它就進入到一個和該對象相關的等待池,同時釋放對象的機鎖,使得其餘線程可以訪問,能夠經過notify,notifyAll方法來喚醒等待的線程


42. notify()和 notifyAll()有什麼區別?


  • 若是線程調用了對象的 wait()方法,那麼線程便會處於該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。

  • 當有線程調用了對象的 notifyAll()方法(喚醒全部 wait 線程)或 notify()方法(只隨機喚醒一個 wait 線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。也就是說,調用了notify後只要一個線程會由等待池進入鎖池,而notifyAll會將該對象等待池內的全部線程移動到鎖池中,等待鎖競爭。

  • 優先級高的線程競爭到對象鎖的機率大,倘若某線程沒有競爭到該對象鎖,它還會留在鎖池中,惟有線程再次調用 wait()方法,它纔會從新回到等待池中。而競爭到對象鎖的線程則繼續往下執行,直到執行完了 synchronized 代碼塊,它會釋放掉該對象鎖,這時鎖池中的線程會繼續競爭該對象鎖。


43. 線程的 run()和 start()有什麼區別?


每一個線程都是經過某個特定Thread對象所對應的方法run()來完成其操做的,方法run()稱爲線程體。經過調用Thread類的start()方法來啓動一個線程。


start()方法來啓動一個線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,能夠直接繼續執行下面的代碼; 這時此線程是處於就緒狀態, 並無運行。 而後經過此Thread類調用方法run()來完成其運行狀態, 這裏方法run()稱爲線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。而後CPU再調度其它線程。


run()方法是在本線程裏的,只是線程裏的一個函數,而不是多線程的。 若是直接調用run(),其實就至關因而調用了一個普通函數而已,直接待用run()方法必須等待run()方法執行完畢才能執行下面的代碼,因此執行路徑仍是隻有一條,根本就沒有線程的特徵,因此在多線程執行時要使用start()方法而不是run()方法。


44. 建立線程池有哪幾種方式?


①. newFixedThreadPool(int nThreads)


建立一個固定長度的線程池,每當提交一個任務就建立一個線程,直到達到線程池的最大數量,這時線程規模將再也不變化,當線程發生未預期的錯誤而結束時,線程池會補充一個新的線程。


②. newCachedThreadPool()


建立一個可緩存的線程池,若是線程池的規模超過了處理需求,將自動回收空閒線程,而當需求增長時,則能夠自動添加新線程,線程池的規模不存在任何限制。


③. newSingleThreadExecutor()


這是一個單線程的Executor,它建立單個工做線程來執行任務,若是這個線程異常結束,會建立一個新的來替代它;它的特色是能確保依照任務在隊列中的順序來串行執行。


④. newScheduledThreadPool(int corePoolSize)


建立了一個固定長度的線程池,並且以延遲或定時的方式來執行任務,相似於Timer。


45. 線程池都有哪些狀態?


線程池有5種狀態:Running、ShutDown、Stop、Tidying、Terminated。

線程池各個狀態切換框架圖:


46. 線程池中 submit()和 execute()方法有什麼區別?


  • 接收的參數不同

  • submit有返回值,而execute沒有

  • submit方便Exception處理


47. 在 java 程序中怎麼保證多線程的運行安全?


線程安全在三個方面體現:


  • 原子性:提供互斥訪問,同一時刻只能有一個線程對數據進行操做,(atomic,synchronized);

  • 可見性:一個線程對主內存的修改能夠及時地被其餘線程看到,(synchronized,volatile);

  • 有序性:一個線程觀察其餘線程中的指令執行順序,因爲指令重排序,該觀察結果通常雜亂無序,(happens-before原則)。


48. 多線程鎖的升級原理是什麼?


在Java中,鎖共有4種狀態,級別從低到高依次爲:無狀態鎖,偏向鎖,輕量級鎖和重量級鎖狀態,這幾個狀態會隨着競爭狀況逐漸升級。鎖能夠升級但不能降級。


鎖升級的圖示過程: 



49. 什麼是死鎖?


死鎖是指兩個或兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。是操做系統層面的一個錯誤,是進程死鎖的簡稱,最先在 1965 年由 Dijkstra 在研究銀行家算法時提出的,它是計算機操做系統乃至整個併發程序設計領域最難處理的問題之一。


50. 怎麼防止死鎖?


死鎖的四個必要條件:


  • 互斥條件:進程對所分配到的資源不容許其餘進程進行訪問,若其餘進程訪問該資源,只能等待,直至佔有該資源的進程使用完成後釋放該資源

  • 請求和保持條件:進程得到必定的資源以後,又對其餘資源發出請求,可是該資源可能被其餘進程佔有,此事請求阻塞,但又對本身得到的資源保持不放

  • 不可剝奪條件:是指進程已得到的資源,在未完成使用以前,不可被剝奪,只能在使用完後本身釋放

  • 環路等待條件:是指進程發生死鎖後,若干進程之間造成一種頭尾相接的循環等待資源關係


這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之 一不知足,就不會發生死鎖。


理解了死鎖的緣由,尤爲是產生死鎖的四個必要條件,就能夠最大可能地避免、預防和 解除死鎖。


因此,在系統設計、進程調度等方面注意如何不讓這四個必要條件成立,如何確 定資源的合理分配算法,避免進程永久佔據系統資源。


此外,也要防止進程在處於等待狀態的狀況下佔用資源。所以,對資源的分配要給予合理的規劃。


51. ThreadLocal 是什麼?有哪些使用場景?


線程局部變量是侷限於線程內部的變量,屬於線程自身全部,不在多個線程間共享。Java提供ThreadLocal類來支持線程局部變量,是一種實現線程安全的方式。可是在管理環境下(如 web 服務器)使用線程局部變量的時候要特別當心,在這種狀況下,工做線程的生命週期比任何應用變量的生命週期都要長。任何線程局部變量一旦在工做完成後沒有釋放,Java 應用就存在內存泄露的風險。


52.說一下 synchronized 底層實現原理?


synchronized能夠保證方法或者代碼塊在運行時,同一時刻只有一個方法能夠進入到臨界區,同時它還能夠保證共享變量的內存可見性。


Java中每個對象均可以做爲鎖,這是synchronized實現同步的基礎:


  • 普通同步方法,鎖是當前實例對象

  • 靜態同步方法,鎖是當前類的class對象

  • 同步方法塊,鎖是括號裏面的對象

53. synchronized 和 volatile 的區別是什麼?


  • volatile本質是在告訴jvm當前變量在寄存器(工做內存)中的值是不肯定的,須要從主存中讀取; synchronized則是鎖定當前變量,只有當前線程能夠訪問該變量,其餘線程被阻塞住。

  • volatile僅能使用在變量級別;synchronized則可使用在變量、方法、和類級別的。

  • volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則能夠保證變量的修改可見性和原子性。

  • volatile不會形成線程的阻塞;synchronized可能會形成線程的阻塞。

  • volatile標記的變量不會被編譯器優化;synchronized標記的變量能夠被編譯器優化。


54. synchronized 和 Lock 有什麼區別?


  • 首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;

  • synchronized沒法判斷是否獲取鎖的狀態,Lock能夠判斷是否獲取到鎖;

  • synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程當中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),不然容易形成線程死鎖;

  • 用synchronized關鍵字的兩個線程1和線程2,若是當前線程1得到鎖,線程2線程等待。若是線程1阻塞,線程2則會一直等待下去,而Lock鎖就不必定會等待下去,若是嘗試獲取不到鎖,線程能夠不用一直等待就結束了;

  • synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(二者皆可);

  • Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少許的同步問題。


55. synchronized 和 ReentrantLock 區別是什麼?


synchronized是和if、else、for、while同樣的關鍵字,ReentrantLock是類,這是兩者的本質區別。既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,能夠被繼承、能夠有方法、能夠有各類各樣的類變量,ReentrantLock比synchronized的擴展性體如今幾點上: 


  • ReentrantLock能夠對獲取鎖的等待時間進行設置,這樣就避免了死鎖 

  • ReentrantLock能夠獲取各類鎖的信息

  • ReentrantLock能夠靈活地實現多路通知 


另外,兩者的鎖機制其實也是不同的:ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操做的應該是對象頭中mark word。

56. 說一下 atomic 的原理?


Atomic包中的類基本的特性就是在多線程環境下,當有多個線程同時對單個(包括基本類型及引用類型)變量進行操做時,具備排他性,即當多個線程同時對該變量的值進行更新時,僅有一個線程能成功,而未成功的線程能夠向自旋鎖同樣,繼續嘗試,一直等到執行成功。


Atomic系列的類中的核心方法都會調用unsafe類中的幾個本地方法。咱們須要先知道一個東西就是Unsafe類,全名爲:sun.misc.Unsafe,這個類包含了大量的對C代碼的操做,包括不少直接內存分配以及原子操做的調用,而它之因此標記爲非安全的,是告訴你這個裏面大量的方法調用都會存在安全隱患,須要當心使用,不然會致使嚴重的後果,例如在經過unsafe分配內存的時候,若是本身指定某些區域可能會致使一些相似C++同樣的指針越界到其餘進程的問題。


反射


57. 什麼是反射?


反射主要是指程序能夠訪問、檢測和修改它自己狀態或行爲的一種能力

Java反射:


在Java運行時環境中,對於任意一個類,可否知道這個類有哪些屬性和方法?對於任意一個對象,可否調用它的任意一個方法

Java反射機制主要提供瞭如下功能:


  • 在運行時判斷任意一個對象所屬的類。

  • 在運行時構造任意一個類的對象。

  • 在運行時判斷任意一個類所具備的成員變量和方法。

  • 在運行時調用任意一個對象的方法。 


58. 什麼是 java 序列化?什麼狀況下須要序列化?


簡單說就是爲了保存在內存中的各類對象的狀態(也就是實例變量,不是方法),而且能夠把保存的對象狀態再讀出來。雖然你能夠用你本身的各類各樣的方法來保存object states,可是Java給你提供一種應該比你本身好的保存對象狀態的機制,那就是序列化。

什麼狀況下須要序列化:


a)當你想把的內存中的對象狀態保存到一個文件中或者數據庫中時候;
b)當你想用套接字在網絡上傳送對象的時候;
c)當你想經過RMI傳輸對象的時候;


59. 動態代理是什麼?有哪些應用?


動態代理:


當想要給實現了某個接口的類中的方法,加一些額外的處理。好比說加日誌,加事務等。能夠給這個類建立一個代理,故名思議就是建立一個新的類,這個類不只包含原來類方法的功能,並且還在原來的基礎上添加了額外處理的新類。這個代理類並非定義好的,是動態生成的。具備解耦意義,靈活,擴展性強。


動態代理的應用:


  • Spring的AOP

  • 加事務

  • 加權限

  • 加日誌


60. 怎麼實現動態代理?


首先必須定義一個接口,還要有一個InvocationHandler(將實現接口的類的對象傳遞給它)處理類。再有一個工具類Proxy(習慣性將其稱爲代理類,由於調用他的newInstance()能夠產生代理對象,其實他只是一個產生代理對象的工具類)。利用到InvocationHandler,拼接代理類源碼,將其編譯生成代理類的二進制碼,利用加載器加載,並將其實例化產生代理對象,最後返回。




第五模塊答案

對象拷貝


61. 爲何要使用克隆?


想對一個對象進行處理,又想保留原有的數據進行接下來的操做,就須要克隆了,Java語言中克隆針對的是類的實例。


62. 如何實現對象克隆?


有兩種方式:


1). 實現Cloneable接口並重寫Object類中的clone()方法;

  

2). 實現Serializable接口,經過對象的序列化和反序列化實現克隆,能夠實現真正的深度克隆,代碼以下:


import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;
public class MyUtil {
private MyUtil() {throw new AssertionError(); }
@SuppressWarnings("unchecked")public static <T extends Serializable> T clone(T obj) throws Exception { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bout); oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bin);return (T) ois.readObject();
// 說明:調用ByteArrayInputStream或ByteArrayOutputStream對象的close方法沒有任何意義// 這兩個基於內存的流只要垃圾回收器清理對象就可以釋放資源,這一點不一樣於對外部資源(如文件流)的釋放 }}


下面是測試代碼:


import java.io.Serializable;
/** * 人類 * @author nnngu * */class Person implements Serializable {private static final long serialVersionUID = -9102017020286042305L;
private String name; // 姓名private int age; // 年齡private Car car; // 座駕
public Person(String name, int age, Car car) {this.name = name;this.age = age;this.car = car; }
public String getName() {return name; }
public void setName(String name) {this.name = name; }
public int getAge() {return age; }
public void setAge(int age) {this.age = age; }
public Car getCar() {return car; }
public void setCar(Car car) {this.car = car; }
@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + ", car=" + car + "]"; }
}


/** * 小汽車類 * @author nnngu * */class Car implements Serializable {private static final long serialVersionUID = -5713945027627603702L;
private String brand; // 品牌private int maxSpeed; // 最高時速
public Car(String brand, int maxSpeed) {this.brand = brand;this.maxSpeed = maxSpeed; }
public String getBrand() {return brand; }
public void setBrand(String brand) {this.brand = brand; }
public int getMaxSpeed() {return maxSpeed; }
public void setMaxSpeed(int maxSpeed) {this.maxSpeed = maxSpeed; }
@Overridepublic String toString() {return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]"; }
}


class CloneTest {
public static void main(String[] args) {try { Person p1 = new Person("郭靖", 33, new Car("Benz", 300)); Person p2 = MyUtil.clone(p1); // 深度克隆 p2.getCar().setBrand("BYD");// 修改克隆的Person對象p2關聯的汽車對象的品牌屬性// 原來的Person對象p1關聯的汽車不會受到任何影響// 由於在克隆Person對象時其關聯的汽車對象也被克隆了 System.out.println(p1); } catch (Exception e) { e.printStackTrace(); } }}


注意:基於序列化和反序列化實現的克隆不只僅是深度克隆,更重要的是經過泛型限定,能夠檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用Object類的clone方法克隆對象。讓問題在編譯的時候暴露出來老是好過把問題留到運行時。


63. 深拷貝和淺拷貝區別是什麼?


  • 淺拷貝只是複製了對象的引用地址,兩個對象指向同一個內存地址,因此修改其中任意的值,另外一個值都會隨之變化,這就是淺拷貝(例:assign())

  • 深拷貝是將對象及值複製過來,兩個對象修改其中任意的值另外一個值不會改變,這就是深拷貝(例:JSON.parse()和JSON.stringify(),可是此方法沒法複製函數類型)

Java Web


64. jsp 和 servlet 有什麼區別?


  1. jsp經編譯後就變成了Servlet.(JSP的本質就是Servlet,JVM只能識別java的類,不能識別JSP的代碼,Web容器將JSP的代碼編譯成JVM可以識別的java類)

  2. jsp更擅長表現於頁面顯示,servlet更擅長於邏輯控制。

  3. Servlet中沒有內置對象,Jsp中的內置對象都是必須經過HttpServletRequest對象,HttpServletResponse對象以及HttpServlet對象獲得。

  4. Jsp是Servlet的一種簡化,使用Jsp只須要完成程序員須要輸出到客戶端的內容,Jsp中的Java腳本如何鑲嵌到一個類中,由Jsp容器完成。而Servlet則是個完整的Java類,這個類的Service方法用於生成對客戶端的響應。


65. jsp 有哪些內置對象?做用分別是什麼?


JSP有9個內置對象:


  • request:封裝客戶端的請求,其中包含來自GET或POST請求的參數;

  • response:封裝服務器對客戶端的響應;

  • pageContext:經過該對象能夠獲取其餘對象;

  • session:封裝用戶會話的對象;

  • application:封裝服務器運行環境的對象;

  • out:輸出服務器響應的輸出流對象;

  • config:Web應用的配置對象;

  • page:JSP頁面自己(至關於Java程序中的this);

  • exception:封裝頁面拋出異常的對象。


66. 說一下 jsp 的 4 種做用域?


JSP中的四種做用域包括page、request、session和application,具體來講:


  • page表明與一個頁面相關的對象和屬性。

  • request表明與Web客戶機發出的一個請求相關的對象和屬性。一個請求可能跨越多個頁面,涉及多個Web組件;須要在頁面顯示的臨時數據能夠置於此做用域。

  • session表明與某個用戶與服務器創建的一次會話相關的對象和屬性。跟某個用戶相關的數據應該放在用戶本身的session中。

  • application表明與整個Web應用程序相關的對象和屬性,它實質上是跨越整個Web應用程序,包括多個頁面、請求和會話的一個全局做用域。


67. session 和 cookie 有什麼區別?


  • 因爲HTTP協議是無狀態的協議,因此服務端須要記錄用戶的狀態時,就須要用某種機制來識具體的用戶,這個機制就是Session.典型的場景好比購物車,當你點擊下單按鈕時,因爲HTTP協議無狀態,因此並不知道是哪一個用戶操做的,因此服務端要爲特定的用戶建立了特定的Session,用用於標識這個用戶,而且跟蹤用戶,這樣才知道購物車裏面有幾本書。這個Session是保存在服務端的,有一個惟一標識。在服務端保存Session的方法不少,內存、數據庫、文件都有。集羣的時候也要考慮Session的轉移,在大型的網站,通常會有專門的Session服務器集羣,用來保存用戶會話,這個時候 Session 信息都是放在內存的,使用一些緩存服務好比Memcached之類的來放 Session。

  • 思考一下服務端如何識別特定的客戶?這個時候Cookie就登場了。每次HTTP請求的時候,客戶端都會發送相應的Cookie信息到服務端。實際上大多數的應用都是用 Cookie 來實現Session跟蹤的,第一次建立Session的時候,服務端會在HTTP協議中告訴客戶端,須要在 Cookie 裏面記錄一個Session ID,之後每次請求把這個會話ID發送到服務器,我就知道你是誰了。有人問,若是客戶端的瀏覽器禁用了 Cookie 怎麼辦?通常這種狀況下,會使用一種叫作URL重寫的技術來進行會話跟蹤,即每次HTTP交互,URL後面都會被附加上一個諸如 sid=xxxxx 這樣的參數,服務端據此來識別用戶。

  • Cookie其實還能夠用在一些方便用戶的場景下,設想你某次登錄過一個網站,下次登陸的時候不想再次輸入帳號了,怎麼辦?這個信息能夠寫到Cookie裏面,訪問網站的時候,網站頁面的腳本能夠讀取這個信息,就自動幫你把用戶名給填了,可以方便一下用戶。這也是Cookie名稱的由來,給用戶的一點甜頭。因此,總結一下:Session是在服務端保存的一個數據結構,用來跟蹤用戶的狀態,這個數據能夠保存在集羣、數據庫、文件中;Cookie是客戶端保存用戶信息的一種機制,用來記錄用戶的一些信息,也是實現Session的一種方式。


68. 說一下 session 的工做原理?


其實session是一個存在服務器上的相似於一個散列表格的文件。裏面存有咱們須要的信息,在咱們須要用的時候能夠從裏面取出來。相似於一個大號的map吧,裏面的鍵存儲的是用戶的sessionid,用戶向服務器發送請求的時候會帶上這個sessionid。這時就能夠從中取出對應的值了。


69. 若是客戶端禁止 cookie 能實現 session 還能用嗎?


Cookie與 Session,通常認爲是兩個獨立的東西,Session採用的是在服務器端保持狀態的方案,而Cookie採用的是在客戶端保持狀態的方案。但爲何禁用Cookie就不能獲得Session呢?由於Session是用Session ID來肯定當前對話所對應的服務器Session,而Session ID是經過Cookie來傳遞的,禁用Cookie至關於失去了Session ID,也就得不到Session了。


假定用戶關閉Cookie的狀況下使用Session,其實現途徑有如下幾種:


  1. 設置php.ini配置文件中的「session.use_trans_sid = 1」,或者編譯時打開打開了「--enable-trans-sid」選項,讓PHP自動跨頁傳遞Session ID。

  2. 手動經過URL傳值、隱藏表單傳遞Session ID。

  3. 用文件、數據庫等形式保存Session ID,在跨頁過程當中手動調用。


70. spring mvc 和 struts 的區別是什麼?


  • 攔截機制的不一樣


Struts2是類級別的攔截,每次請求就會建立一個Action,和Spring整合時Struts2的ActionBean注入做用域是原型模式prototype,而後經過setter,getter吧request數據注入到屬性。Struts2中,一個Action對應一個request,response上下文,在接收參數時,能夠經過屬性接收,這說明屬性參數是讓多個方法共享的。Struts2中Action的一個方法能夠對應一個url,而其類屬性卻被全部方法共享,這也就沒法用註解或其餘方式標識其所屬方法了,只能設計爲多例。

  

SpringMVC是方法級別的攔截,一個方法對應一個Request上下文,因此方法直接基本上是獨立的,獨享request,response數據。而每一個方法同時又何一個url對應,參數的傳遞是直接注入到方法中的,是方法所獨有的。處理結果經過ModeMap返回給框架。在Spring整合時,SpringMVC的Controller Bean默認單例模式Singleton,因此默認對全部的請求,只會建立一個Controller,有應爲沒有共享的屬性,因此是線程安全的,若是要改變默認的做用域,須要添加@Scope註解修改。

  

Struts2有本身的攔截Interceptor機制,SpringMVC這是用的是獨立的Aop方式,這樣致使Struts2的配置文件量仍是比SpringMVC大。


  • 底層框架的不一樣

  

Struts2採用Filter(StrutsPrepareAndExecuteFilter)實現,SpringMVC(DispatcherServlet)則採用Servlet實現。Filter在容器啓動以後即初始化;服務中止之後墜毀,晚於Servlet。Servlet在是在調用時初始化,先於Filter調用,服務中止後銷燬。


  • 性能方面


Struts2是類級別的攔截,每次請求對應實例一個新的Action,須要加載全部的屬性值注入,SpringMVC實現了零配置,因爲SpringMVC基於方法的攔截,有加載一次單例模式bean注入。因此,SpringMVC開發效率和性能高於Struts2。


  • 配置方面

  

spring MVC和Spring是無縫的。從這個項目的管理和安全上也比Struts2高。


71. 如何避免 sql 注入?


  1. PreparedStatement(簡單又有效的方法)

  2. 使用正則表達式過濾傳入的參數

  3. 字符串過濾

  4. JSP中調用該函數檢查是否包函非法字符

  5. JSP頁面判斷代碼


72. 什麼是 XSS 攻擊,如何避免?

XSS攻擊又稱CSS,全稱Cross Site Script  (跨站腳本攻擊),其原理是攻擊者向有XSS漏洞的網站中輸入惡意的 HTML 代碼,當用戶瀏覽該網站時,這段 HTML 代碼會自動執行,從而達到攻擊的目的。XSS 攻擊相似於 SQL 注入攻擊,SQL注入攻擊中以SQL語句做爲用戶輸入,從而達到查詢/修改/刪除數據的目的,而在xss攻擊中,經過插入惡意腳本,實現對用戶遊覽器的控制,獲取用戶的一些信息。 XSS是 Web 程序中常見的漏洞,XSS 屬於被動式且用於客戶端的攻擊方式。


XSS防範的整體思路是:對輸入(和URL參數)進行過濾,對輸出進行編碼。


73. 什麼是 CSRF 攻擊,如何避免?


CSRF(Cross-site request forgery)也被稱爲 one-click attack或者 session riding,中文全稱是叫跨站請求僞造。通常來講,攻擊者經過僞造用戶的瀏覽器的請求,向訪問一個用戶本身曾經認證訪問過的網站發送出去,使目標網站接收並誤覺得是用戶的真實操做而去執行命令。經常使用於盜取帳號、轉帳、發送虛假消息等。攻擊者利用網站對請求的驗證漏洞而實現這樣的攻擊行爲,網站可以確認請求來源於用戶的瀏覽器,卻不能驗證請求是否源於用戶的真實意願下的操做行爲。


如何避免:


1. 驗證 HTTP Referer 字段


HTTP頭中的Referer字段記錄了該 HTTP 請求的來源地址。在一般狀況下,訪問一個安全受限頁面的請求來自於同一個網站,而若是黑客要對其實施 CSRF
攻擊,他通常只能在他本身的網站構造請求。所以,能夠經過驗證Referer值來防護CSRF 攻擊。


2. 使用驗證碼


關鍵操做頁面加上驗證碼,後臺收到請求後經過判斷驗證碼能夠防護CSRF。但這種方法對用戶不太友好。


3. 在請求地址中添加token並驗證


CSRF 攻擊之因此可以成功,是由於黑客能夠徹底僞造用戶的請求,該請求中全部的用戶驗證信息都是存在於cookie中,所以黑客能夠在不知道這些驗證信息的狀況下直接利用用戶本身的cookie 來經過安全驗證。要抵禦 CSRF,關鍵在於在請求中放入黑客所不能僞造的信息,而且該信息不存在於 cookie 之中。能夠在 HTTP 請求中以參數的形式加入一個隨機產生的 token,並在服務器端創建一個攔截器來驗證這個 token,若是請求中沒有token或者 token 內容不正確,則認爲多是 CSRF 攻擊而拒絕該請求。這種方法要比檢查 Referer 要安全一些,token 能夠在用戶登錄後產生並放於session之中,而後在每次請求時把token 從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在於如何把 token 以參數的形式加入請求。
對於 GET 請求,token 將附在請求地址以後,這樣 URL 就變成 http://url?csrftoken=tokenvalue。
而對於 POST 請求來講,要在 form 的最後加上 <input type="hidden" name="csrftoken" value="tokenvalue"/>,這樣就把token以參數的形式加入請求了。


4. 在HTTP 頭中自定義屬性並驗證


這種方法也是使用 token 並進行驗證,和上一種方法不一樣的是,這裏並非把 token 以參數的形式置於 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性裏。經過 XMLHttpRequest 這個類,能夠一次性給全部該類請求加上 csrftoken 這個 HTTP 頭屬性,並把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,經過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用擔憂 token 會透過 Referer 泄露到其餘網站中去。

以爲不錯的歡迎關注公衆號 程序員也幽默 分享乾貨和段子!記得點在看和轉發一下哦! 

往期精彩


01 漫談發版哪些事,好課程推薦

02 Linux的經常使用最危險的命令

03 精講Spring&nbsp;Boot—入門+進階+實例

04 優秀的Java程序員必須瞭解的GC哪些

05 互聯網支付系統總體架構詳解

關注我

天天進步一點點

很乾!在看嗎?☟

本文分享自微信公衆號 - JAVA樂園(happyhuangjinjin88)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索