1. JDK 和 JRE 有什么區(qū)別?
JDK:Java Development Kit 的簡稱,java 開發(fā)工具包,提供了 java 的開發(fā)環(huán)境和運行環(huán)境。JRE:Java Runtime Environment 的簡稱,java 運行環(huán)境,為 java 的運行提供了所需環(huán)境。具體來說 JDK 其實包含了 JRE,同時還包含了編譯 java 源碼的編譯器 javac,還包含了很多 java 程序調(diào)試和分析的工具。簡單來說:如果你需要運行 java 程序,只需安裝 JRE 就可以了,如果你需要編寫 java 程序,需要安裝 JDK。
2. == 和 equals 的區(qū)別是什么?
== 解讀
對于基本類型和引用類型 == 的作用效果是不同的,如下所示:
基本類型:比較的是值是否相同;引用類型:比較的是引用是否相同;代碼示例:
String x = "string"; String y = "string"; String z = new String("string"); System.out.println(x==y); // true System.out.println(x==z); // false System.out.println(x.equals(y)); // true System.out.println(x.equals(z)); // true
代碼解讀:因為 x 和 y 指向的是同一個引用,所以 == 也是 true,而 new String()方法則重寫開辟了內(nèi)存空間,所以 == 結(jié)果為 false,而 equals 比較的一直是值,所以結(jié)果都為 true。
equals 解讀
equals 本質(zhì)上就是 ==,只不過 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
輸出結(jié)果出乎我們的意料,竟然是 false?這是怎么回事,看了 equals 源碼就知道了,源碼如下:
public boolean equals(Object obj) { return (this == obj); }
原來 equals 本質(zhì)上就是 ==。
那問題來了,兩個相同值的 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 方法,把引用比較改成了值比較。
總結(jié) :== 對于基本類型來說是值比較,對于引用類型來說是比較的是引用;而 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));
執(zhí)行的結(jié)果:
str1:1179395 | str2:1179395 false
代碼解讀:很顯然“通話”和“重地”的 hashCode() 相同,然而 equals() 則為 false,因為在散列表中,hashCode()相等即兩個鍵值對的哈希值相等,然而哈希值相等,并不一定能得出鍵值對相等。
4. final 在 java 中有什么作用?
final 修飾的類叫最終類,該類不能被繼承。final 修飾的方法不能被重寫。final 修飾的變量叫常量,常量必須初始化,初始化之后值就不能被修改。5. java 中的 Math.round(-1.5) 等于多少?
等于 -1,因為在數(shù)軸上取值時,中間值(0.5)向右取整,所以正 0.5 是往上取整,負 0.5 是直接舍棄。
6. String 屬于基礎的數(shù)據(jù)類型嗎?
String 不屬于基礎類型,基礎類型有 8 種:byte、boolean、char、short、int、float、long、double,而 String 屬于對象。
7. java 中操作字符串都有哪些類?它們之間有什么區(qū)別?
操作字符串的類有:String、StringBuffer、StringBuilder。
String 和 StringBuffer、StringBuilder 的區(qū)別在于 String 聲明的是不可變的對象,每次操作都會生成新的 String 對象,然后將指針指向新的 String 對象,而 StringBuffer、StringBuilder 可以在原有對象的基礎上進行操作,所以在經(jīng)常改變字符串內(nèi)容的情況下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的區(qū)別在于,StringBuffer 是線程安全的,而 StringBuilder 是非線程安全的,但 StringBuilder 的性能卻高于 StringBuffer,所以在單線程環(huán)境下推薦使用 StringBuilder,多線程環(huán)境下推薦使用 StringBuffer。
博主已將這些面試題整理到一個網(wǎng)站上,每天更新 Java 面試題,目前有 2000 道 Java 高頻面試題。
8. String str="i"與 String str=new String("i")一樣嗎?
不一樣,因為內(nèi)存的分配方式不一樣。String str="i"的方式,java 虛擬機會將其分配到常量池中;而 String str=new String("i") 則會被分到堆內(nèi)存中。
9. 如何將字符串反轉(zhuǎn)?
使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
示例代碼:
// StringBuffer reverse StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("abcdefg"); System.out.println(stringBuffer.reverse()); // gfedcba // StringBuilder reverse StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("abcdefg"); System.out.println(stringBuilder.reverse()); // gfedcba
10. String 類的常用方法都有那些?
indexOf():返回指定字符的索引。charAt():返回指定索引處的字符。replace():字符串替換。trim():去除字符串兩端空白。split():分割字符串,返回一個分割后的字符串數(shù)組。getBytes():返回字符串的 byte 類型數(shù)組。length():返回字符串長度。toLowerCase():將字符串轉(zhuǎn)成小寫字母。toUpperCase():將字符串轉(zhuǎn)成大寫字符。substring():截取字符串。equals():字符串比較。11. 抽象類必須要有抽象方法嗎?
不需要,抽象類不一定非要有抽象方法。
示例代碼:
abstract class Cat { public static void sayHi() { System.out.println("hi~"); } }
上面代碼,抽象類并沒有抽象方法但完全可以正常運行。
12. 普通類和抽象類有哪些區(qū)別?
普通類不能包含抽象方法,抽象類可以包含抽象方法。抽象類不能直接實例化,普通類可以直接實例化。13. 抽象類能使用 final 修飾嗎?
不能,定義抽象類就是讓其他類繼承的,如果定義為 final 該類就不能被繼承,這樣彼此就會產(chǎn)生矛盾,所以 final 不能修飾抽象類,如下圖所示,編輯器也會提示錯誤信息:
14. 接口和抽象類有什么區(qū)別?
實現(xiàn):抽象類的子類使用 extends 來繼承;接口必須使用 implements 來實現(xiàn)接口。構(gòu)造函數(shù):抽象類可以有構(gòu)造函數(shù);接口不能有。main 方法:抽象類可以有 main 方法,并且我們能運行它;接口不能有 main 方法。實現(xiàn)數(shù)量:類可以實現(xiàn)很多個接口;但是只能繼承一個抽象類。訪問修飾符:接口中的方法默認使用 public 修飾;抽象類中的方法可以是任意訪問修飾符。15. java 中 IO 流分為幾種?
按功能來分:輸入流(input)、輸出流(output)。
按類型來分:字節(jié)流和字符流。
字節(jié)流和字符流的區(qū)別是:字節(jié)流按 8 位傳輸以字節(jié)為單位輸入輸出數(shù)據(jù),字符流按 16 位傳輸以字符為單位輸入輸出數(shù)據(jù)。
16. BIO、NIO、AIO 有什么區(qū)別?
BIO:Block IO 同步阻塞式 IO,就是我們平常使用的傳統(tǒng) IO,它的特點是模式簡單使用方便,并發(fā)處理能力低。NIO:New IO 同步非阻塞 IO,是傳統(tǒng) IO 的升級,客戶端和服務器端通過 Channel(通道)通訊,實現(xiàn)了多路復用。AIO:Asynchronous IO 是 NIO 的升級,也叫 NIO2,實現(xiàn)了異步非堵塞 IO ,異步 IO 的操作基于事件和回調(diào)機制。17. Files的常用方法都有哪些?
Files.exists():檢測文件路徑是否存在。Files.createFile():創(chuàng)建文件。Files.createDirectory():創(chuàng)建文件夾。Files.delete():刪除一個文件或目錄。Files.copy():復制文件。Files.move():移動文件。Files.size():查看文件個數(shù)。Files.read():讀取文件。Files.write():寫入文件。二、容器
18. java 容器都有哪些?
常用容器的圖錄:
19. Collection 和 Collections 有什么區(qū)別?
java.util.Collection 是一個集合接口(集合類的一個頂級接口)。它提供了對集合對象進行基本操作的通用接口方法。Collection接口在Java 類庫中有很多具體的實現(xiàn)。Collection接口的意義是為各種具體的集合提供了最大化的統(tǒng)一操作方式,其直接繼承接口有List與Set。Collections則是集合類的一個工具類/幫助類,其中提供了一系列靜態(tài)方法,用于對集合中元素進行排序、搜索以及線程安全等各種操作。20. List、Set、Map 之間的區(qū)別是什么?
21. HashMap 和 Hashtable 有什么區(qū)別?
hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。hashTable同步的,而HashMap是非同步的,效率上逼hashTable要高。hashMap允許空鍵值,而hashTable不允許。22. 如何決定使用 HashMap 還是 TreeMap?
對于在Map中插入、刪除和定位元素這類操作,HashMap是最好的選擇。然而,假如你需要對一個有序的key集合進行遍歷,TreeMap是更好的選擇。基于你的collection的大小,也許向HashMap中添加元素會更快,將map換為TreeMap進行有序key的遍歷。
23. 說一下 HashMap 的實現(xiàn)原理?
HashMap概述: HashMap是基于哈希表的Map接口的非同步實現(xiàn)。此實現(xiàn)提供所有可選的映射操作,并允許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恒久不變。
HashMap的數(shù)據(jù)結(jié)構(gòu): 在java編程語言中,最基本的結(jié)構(gòu)就是兩種,一個是數(shù)組,另外一個是模擬指針(引用),所有的數(shù)據(jù)結(jié)構(gòu)都可以用這兩個基本結(jié)構(gòu)來構(gòu)造的,HashMap也不例外。HashMap實際上是一個“鏈表散列”的數(shù)據(jù)結(jié)構(gòu),即數(shù)組和鏈表的結(jié)合體。
當我們往Hashmap中put元素時,首先根據(jù)key的hashcode重新計算hash值,根絕hash值得到這個元素在數(shù)組中的位置(下標),如果該數(shù)組在該位置上已經(jīng)存放了其他元素,那么在這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最先加入的放入鏈尾.如果數(shù)組中該位置沒有元素,就直接將該元素放到數(shù)組的該位置上。
需要注意Jdk 1.8中對HashMap的實現(xiàn)做了優(yōu)化,當鏈表中的節(jié)點數(shù)據(jù)超過八個之后,該鏈表會轉(zhuǎn)為紅黑樹來提高查詢效率,從原來的O(n)到O(logn)
24. 說一下 HashSet 的實現(xiàn)原理?
HashSet底層由HashMap實現(xiàn)HashSet的值存放于HashMap的key上HashMap的value統(tǒng)一為PRESENT25. ArrayList 和 LinkedList 的區(qū)別是什么?
最明顯的區(qū)別是 ArrrayList底層的數(shù)據(jù)結(jié)構(gòu)是數(shù)組,支持隨機訪問,而 LinkedList 的底層數(shù)據(jù)結(jié)構(gòu)是雙向循環(huán)鏈表,不支持隨機訪問。使用下標訪問一個元素,ArrayList 的時間復雜度是 O(1),而 LinkedList 是 O(n)。
26. 如何實現(xiàn)數(shù)組和 List 之間的轉(zhuǎn)換?
List轉(zhuǎn)換成為數(shù)組:調(diào)用ArrayList的toArray方法。數(shù)組轉(zhuǎn)換成為List:調(diào)用Arrays的asList方法。27. ArrayList 和 Vector 的區(qū)別是什么?
Vector是同步的,而ArrayList不是。然而,如果你尋求在迭代的時候?qū)α斜磉M行改變,你應該使用CopyOnWriteArrayList。ArrayList比Vector快,它因為有同步,不會過載。ArrayList更加通用,因為我們可以使用Collections工具類輕易地獲取同步列表和只讀列表。28. Array 和 ArrayList 有何區(qū)別?
Array可以容納基本類型和對象,而ArrayList只能容納對象。Array是指定大小的,而ArrayList大小是固定的。Array沒有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。29. 在 Queue 中 poll()和 remove()有什么區(qū)別?
poll() 和 remove() 都是從隊列中取出一個元素,但是 poll() 在獲取元素失敗的時候會返回空,但是 remove() 失敗的時候會拋出異常。
30. 哪些集合類是線程安全的?
vector:就比arraylist多了個同步化機制(線程安全),因為效率較低,現(xiàn)在已經(jīng)不太建議使用。在web應用中,特別是前臺頁面,往往效率(頁面響應速度)是優(yōu)先考慮的。statck:堆棧類,先進后出。hashtable:就比hashmap多了個線程安全。enumeration:枚舉,相當于迭代器。31. 迭代器 Iterator 是什么?
迭代器是一種設計模式,它是一個對象,它可以遍歷并選擇序列中的對象,而開發(fā)人員不需要了解該序列的底層結(jié)構(gòu)。迭代器通常被稱為“輕量級”對象,因為創(chuàng)建它的代價小。
32. Iterator 怎么使用?有什么特點?
Java中的Iterator功能比較簡單,并且只能單向移動:
(1) 使用方法iterator()要求容器返回一個Iterator。第一次調(diào)用Iterator的next()方法時,它返回序列的第一個元素。注意:iterator()方法是java.lang.Iterable接口,被Collection繼承。
(2) 使用next()獲得序列中的下一個元素。
(3) 使用hasNext()檢查序列中是否還有元素。
(4) 使用remove()將迭代器新返回的元素刪除。
Iterator是Java迭代器最簡單的實現(xiàn),為List設計的ListIterator具有更多的功能,它可以從兩個方向遍歷List,也可以從List中插入和刪除元素。
33. Iterator 和 ListIterator 有什么區(qū)別?
Iterator可用來遍歷Set和List集合,但是ListIterator只能用來遍歷List。Iterator對集合只能是前向遍歷,ListIterator既可以前向也可以后向。ListIterator實現(xiàn)了Iterator接口,并包含其他的功能,比如:增加元素,替換元素,獲取前一個和后一個元素的索引,等等。三、多線程
35. 并行和并發(fā)有什么區(qū)別?
并行是指兩個或者多個事件在同一時刻發(fā)生;而并發(fā)是指兩個或多個事件在同一時間間隔發(fā)生。并行是在不同實體上的多個事件,并發(fā)是在同一實體上的多個事件。在一臺處理器上“同時”處理多個任務,在多臺處理器上同時處理多個任務。如hadoop分布式集群。所以并發(fā)編程的目標是充分的利用處理器的每一個核,以達到最高的處理性能。
36. 線程和進程的區(qū)別?
簡而言之,進程是程序運行和資源分配的基本單位,一個程序至少有一個進程,一個進程至少有一個線程。進程在執(zhí)行過程中擁有獨立的內(nèi)存單元,而多個線程共享內(nèi)存資源,減少切換次數(shù),從而效率更高。線程是進程的一個實體,是cpu調(diào)度和分派的基本單位,是比程序更小的能獨立運行的基本單位。同一進程中的多個線程之間可以并發(fā)執(zhí)行。
37. 守護線程是什么?
守護線程(即daemon thread),是個服務線程,準確地來說就是服務其他的線程。
38. 創(chuàng)建線程有哪幾種方式?
①. 繼承Thread類創(chuàng)建線程類
定義Thread類的子類,并重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務。因此把run()方法稱為執(zhí)行體。創(chuàng)建Thread子類的實例,即創(chuàng)建了線程對象。調(diào)用線程對象的start()方法來啟動該線程。②. 通過Runnable接口創(chuàng)建線程類
定義runnable接口的實現(xiàn)類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執(zhí)行體。創(chuàng)建 Runnable實現(xiàn)類的實例,并依此實例作為Thread的target來創(chuàng)建Thread對象,該Thread對象才是真正的線程對象。調(diào)用線程對象的start()方法來啟動該線程。③. 通過Callable和Future創(chuàng)建線程
創(chuàng)建Callable接口的實現(xiàn)類,并實現(xiàn)call()方法,該call()方法將作為線程執(zhí)行體,并且有返回值。創(chuàng)建Callable實現(xiàn)類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。使用FutureTask對象作為Thread對象的target創(chuàng)建并啟動新線程。調(diào)用FutureTask對象的get()方法來獲得子線程執(zhí)行結(jié)束后的返回值。39. 說一下 runnable 和 callable 有什么區(qū)別?
有點深的問題了,也看出一個Java程序員學習知識的廣度。
Runnable接口中的run()方法的返回值是void,它做的事情只是純粹地去執(zhí)行run()方法中的代碼而已;Callable接口中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合可以用來獲取異步執(zhí)行的結(jié)果。40. 線程有哪些狀態(tài)?
線程通常都有五種狀態(tài),創(chuàng)建、就緒、運行、阻塞和死亡。
創(chuàng)建狀態(tài)。在生成線程對象,并沒有調(diào)用該對象的start方法,這是線程處于創(chuàng)建狀態(tài)。就緒狀態(tài)。當調(diào)用了線程對象的start方法之后,該線程就進入了就緒狀態(tài),但是此時線程調(diào)度程序還沒有把該線程設置為當前線程,此時處于就緒狀態(tài)。在線程運行之后,從等待或者睡眠中回來之后,也會處于就緒狀態(tài)。運行狀態(tài)。線程調(diào)度程序?qū)⑻幱诰途w狀態(tài)的線程設置為當前線程,此時線程就進入了運行狀態(tài),開始運行run函數(shù)當中的代碼。阻塞狀態(tài)。線程正在運行的時候,被暫停,通常是為了等待某個時間的發(fā)生(比如說某項資源就緒)之后再繼續(xù)運行。sleep,suspend,wait等方法都可以導致線程阻塞。死亡狀態(tài)。如果一個線程的run方法執(zhí)行結(jié)束或者調(diào)用stop方法后,該線程就會死亡。對于已經(jīng)死亡的線程,無法再使用start方法令其進入就緒41. sleep() 和 wait() 有什么區(qū)別?
sleep():方法是線程類(Thread)的靜態(tài)方法,讓調(diào)用線程進入睡眠狀態(tài),讓出執(zhí)行機會給其他線程,等到休眠時間結(jié)束后,線程進入就緒狀態(tài)和其他線程一起競爭cpu的執(zhí)行時間。因為sleep() 是static靜態(tài)的方法,他不能改變對象的機鎖,當一個synchronized塊中調(diào)用了sleep() 方法,線程雖然進入休眠,但是對象的機鎖沒有被釋放,其他線程依然無法訪問這個對象。
wait():wait()是Object類的方法,當一個線程執(zhí)行到wait方法時,它就進入到一個和該對象相關(guān)的等待池,同時釋放對象的機鎖,使得其他線程能夠訪問,可以通過notify,notifyAll方法來喚醒等待的線程。
42. notify()和 notifyAll()有什么區(qū)別?
如果線程調(diào)用了對象的 wait()方法,那么線程便會處于該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。當有線程調(diào)用了對象的 notifyAll()方法(喚醒所有 wait 線程)或 notify()方法(只隨機喚醒一個 wait 線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。也就是說,調(diào)用了notify后只要一個線程會由等待池進入鎖池,而notifyAll會將該對象等待池內(nèi)的所有線程移動到鎖池中,等待鎖競爭。優(yōu)先級高的線程競爭到對象鎖的概率大,假若某線程沒有競爭到該對象鎖,它還會留在鎖池中,唯有線程再次調(diào)用 wait()方法,它才會重新回到等待池中。而競爭到對象鎖的線程則繼續(xù)往下執(zhí)行,直到執(zhí)行完了 synchronized 代碼塊,它會釋放掉該對象鎖,這時鎖池中的線程會繼續(xù)競爭該對象鎖。43. 線程的 run()和 start()有什么區(qū)別?
每個線程都是通過某個特定Thread對象所對應的方法run()來完成其操作的,方法run()稱為線程體。通過調(diào)用Thread類的start()方法來啟動一個線程。
start()方法來啟動一個線程,真正實現(xiàn)了多線程運行。這時無需等待run方法體代碼執(zhí)行完畢,可以直接繼續(xù)執(zhí)行下面的代碼; 這時此線程是處于就緒狀態(tài), 并沒有運行。 然后通過此Thread類調(diào)用方法run()來完成其運行狀態(tài), 這里方法run()稱為線程體,它包含了要執(zhí)行的這個線程的內(nèi)容, Run方法運行結(jié)束, 此線程終止。然后CPU再調(diào)度其它線程。
run()方法是在本線程里的,只是線程里的一個函數(shù),而不是多線程的。 如果直接調(diào)用run(),其實就相當于是調(diào)用了一個普通函數(shù)而已,直接待用run()方法必須等待run()方法執(zhí)行完畢才能執(zhí)行下面的代碼,所以執(zhí)行路徑還是只有一條,根本就沒有線程的特征,所以在多線程執(zhí)行時要使用start()方法而不是run()方法。
44. 創(chuàng)建線程池有哪幾種方式?
①. newFixedThreadPool(int nThreads)
創(chuàng)建一個固定長度的線程池,每當提交一個任務就創(chuàng)建一個線程,直到達到線程池的最大數(shù)量,這時線程規(guī)模將不再變化,當線程發(fā)生未預期的錯誤而結(jié)束時,線程池會補充一個新的線程。
②. newCachedThreadPool()
創(chuàng)建一個可緩存的線程池,如果線程池的規(guī)模超過了處理需求,將自動回收空閑線程,而當需求增加時,則可以自動添加新線程,線程池的規(guī)模不存在任何限制。
③. newSingleThreadExecutor()
這是一個單線程的Executor,它創(chuàng)建單個工作線程來執(zhí)行任務,如果這個線程異常結(jié)束,會創(chuàng)建一個新的來替代它;它的特點是能確保依照任務在隊列中的順序來串行執(zhí)行。
④. newScheduledThreadPool(int corePoolSize)
創(chuàng)建了一個固定長度的線程池,而且以延遲或定時的方式來執(zhí)行任務,類似于Timer。
45. 線程池都有哪些狀態(tài)?
線程池有5種狀態(tài):Running、ShutDown、Stop、Tidying、Terminated。
線程池各個狀態(tài)切換框架圖:
46. 線程池中 submit()和 execute()方法有什么區(qū)別?
接收的參數(shù)不一樣submit有返回值,而execute沒有submit方便Exception處理47. 在 java 程序中怎么保證多線程的運行安全?
線程安全在三個方面體現(xiàn):
原子性:提供互斥訪問,同一時刻只能有一個線程對數(shù)據(jù)進行操作,(atomic,synchronized);可見性:一個線程對主內(nèi)存的修改可以及時地被其他線程看到,(synchronized,volatile);有序性:一個線程觀察其他線程中的指令執(zhí)行順序,由于指令重排序,該觀察結(jié)果一般雜亂無序,(happens-before原則)。48. 多線程鎖的升級原理是什么?
在Java中,鎖共有4種狀態(tài),級別從低到高依次為:無狀態(tài)鎖,偏向鎖,輕量級鎖和重量級鎖狀態(tài),這幾個狀態(tài)會隨著競爭情況逐漸升級。鎖可以升級但不能降級。
鎖升級的圖示過程:
49. 什么是死鎖?
死鎖是指兩個或兩個以上的進程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。是操作系統(tǒng)層面的一個錯誤,是進程死鎖的簡稱,最早在 1965 年由 Dijkstra 在研究銀行家算法時提出的,它是計算機操作系統(tǒng)乃至整個并發(fā)程序設計領(lǐng)域最難處理的問題之一。
50. 怎么防止死鎖?
死鎖的四個必要條件:
互斥條件:進程對所分配到的資源不允許其他進程進行訪問,若其他進程訪問該資源,只能等待,直至占有該資源的進程使用完成后釋放該資源請求和保持條件:進程獲得一定的資源之后,又對其他資源發(fā)出請求,但是該資源可能被其他進程占有,此事請求阻塞,但又對自己獲得的資源保持不放不可剝奪條件:是指進程已獲得的資源,在未完成使用之前,不可被剝奪,只能在使用完后自己釋放環(huán)路等待條件:是指進程發(fā)生死鎖后,若干進程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系這四個條件是死鎖的必要條件,只要系統(tǒng)發(fā)生死鎖,這些條件必然成立,而只要上述條件之 一不滿足,就不會發(fā)生死鎖。
理解了死鎖的原因,尤其是產(chǎn)生死鎖的四個必要條件,就可以最大可能地避免、預防和 解除死鎖。
所以,在系統(tǒng)設計、進程調(diào)度等方面注意如何不讓這四個必要條件成立,如何確 定資源的合理分配算法,避免進程永久占據(jù)系統(tǒng)資源。
此外,也要防止進程在處于等待狀態(tài)的情況下占用資源。因此,對資源的分配要給予合理的規(guī)劃。
51. ThreadLocal 是什么?有哪些使用場景?
線程局部變量是局限于線程內(nèi)部的變量,屬于線程自身所有,不在多個線程間共享。Java提供ThreadLocal類來支持線程局部變量,是一種實現(xiàn)線程安全的方式。但是在管理環(huán)境下(如 web 服務器)使用線程局部變量的時候要特別小心,在這種情況下,工作線程的生命周期比任何應用變量的生命周期都要長。任何線程局部變量一旦在工作完成后沒有釋放,Java 應用就存在內(nèi)存泄露的風險。
52.說一下 synchronized 底層實現(xiàn)原理?
synchronized可以保證方法或者代碼塊在運行時,同一時刻只有一個方法可以進入到臨界區(qū),同時它還可以保證共享變量的內(nèi)存可見性。
Java中每一個對象都可以作為鎖,這是synchronized實現(xiàn)同步的基礎:
普通同步方法,鎖是當前實例對象靜態(tài)同步方法,鎖是當前類的class對象同步方法塊,鎖是括號里面的對象53. synchronized 和 volatile 的區(qū)別是什么?
volatile本質(zhì)是在告訴jvm當前變量在寄存器(工作內(nèi)存)中的值是不確定的,需要從主存中讀取; synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的。volatile僅能實現(xiàn)變量的修改可見性,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性。volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。volatile標記的變量不會被編譯器優(yōu)化;synchronized標記的變量可以被編譯器優(yōu)化。54. synchronized 和 Lock 有什么區(qū)別?
首先synchronized是java內(nèi)置關(guān)鍵字,在jvm層面,Lock是個java類;synchronized無法判斷是否獲取鎖的狀態(tài),Lock可以判斷是否獲取到鎖;synchronized會自動釋放鎖(a 線程執(zhí)行完同步代碼會釋放鎖 ;b 線程執(zhí)行過程中發(fā)生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖;用synchronized關(guān)鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結(jié)束了;synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可);Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。55. synchronized 和 ReentrantLock 區(qū)別是什么?
synchronized是和if、else、for、while一樣的關(guān)鍵字,ReentrantLock是類,這是二者的本質(zhì)區(qū)別。既然ReentrantLock是類,那么它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現(xiàn)在幾點上:
ReentrantLock可以對獲取鎖的等待時間進行設置,這樣就避免了死鎖ReentrantLock可以獲取各種鎖的信息ReentrantLock可以靈活地實現(xiàn)多路通知另外,二者的鎖機制其實也是不一樣的:ReentrantLock底層調(diào)用的是Unsafe的park方法加鎖,synchronized操作的應該是對象頭中mark word。
56. 說一下 atomic 的原理?
Atomic包中的類基本的特性就是在多線程環(huán)境下,當有多個線程同時對單個(包括基本類型及引用類型)變量進行操作時,具有排他性,即當多個線程同時對該變量的值進行更新時,僅有一個線程能成功,而未成功的線程可以向自旋鎖一樣,繼續(xù)嘗試,一直等到執(zhí)行成功。
Atomic系列的類中的核心方法都會調(diào)用unsafe類中的幾個本地方法。我們需要先知道一個東西就是Unsafe類,全名為:sun.misc.Unsafe,這個類包含了大量的對C代碼的操作,包括很多直接內(nèi)存分配以及原子操作的調(diào)用,而它之所以標記為非安全的,是告訴你這個里面大量的方法調(diào)用都會存在安全隱患,需要小心使用,否則會導致嚴重的后果,例如在通過unsafe分配內(nèi)存的時候,如果自己指定某些區(qū)域可能會導致一些類似C++一樣的指針越界到其他進程的問題。
四、反射
57. 什么是反射?
反射主要是指程序可以訪問、檢測和修改它本身狀態(tài)或行為的一種能力
Java反射:
在Java運行時環(huán)境中,對于任意一個類,能否知道這個類有哪些屬性和方法?對于任意一個對象,能否調(diào)用它的任意一個方法
Java反射機制主要提供了以下功能:
在運行時判斷任意一個對象所屬的類。在運行時構(gòu)造任意一個類的對象。在運行時判斷任意一個類所具有的成員變量和方法。在運行時調(diào)用任意一個對象的方法。58. 什么是 java 序列化?什么情況下需要序列化?
簡單說就是為了保存在內(nèi)存中的各種對象的狀態(tài)(也就是實例變量,不是方法),并且可以把保存的對象狀態(tài)再讀出來。雖然你可以用你自己的各種各樣的方法來保存object states,但是Java給你提供一種應該比你自己好的保存對象狀態(tài)的機制,那就是序列化。 什么情況下需要序列化:
a)當你想把的內(nèi)存中的對象狀態(tài)保存到一個文件中或者數(shù)據(jù)庫中時候; b)當你想用套接字在網(wǎng)絡上傳送對象的時候; c)當你想通過RMI傳輸對象的時候;
59. 動態(tài)代理是什么?有哪些應用?
動態(tài)代理:
當想要給實現(xiàn)了某個接口的類中的方法,加一些額外的處理。比如說加日志,加事務等??梢越o這個類創(chuàng)建一個代理,故名思議就是創(chuàng)建一個新的類,這個類不僅包含原來類方法的功能,而且還在原來的基礎上添加了額外處理的新類。這個代理類并不是定義好的,是動態(tài)生成的。具有解耦意義,靈活,擴展性強。
動態(tài)代理的應用:
Spring的AOP加事務加權(quán)限加日志60. 怎么實現(xiàn)動態(tài)代理?
首先必須定義一個接口,還要有一個InvocationHandler(將實現(xiàn)接口的類的對象傳遞給它)處理類。再有一個工具類Proxy(習慣性將其稱為代理類,因為調(diào)用他的newInstance()可以產(chǎn)生代理對象,其實他只是一個產(chǎn)生代理對象的工具類)。利用到InvocationHandler,拼接代理類源碼,將其編譯生成代理類的二進制碼,利用加載器加載,并將其實例化產(chǎn)生代理對象,最后返回。
五、對象拷貝61. 為什么要使用克???
想對一個對象進行處理,又想保留原有的數(shù)據(jù)進行接下來的操作,就需要克隆了,Java語言中克隆針對的是類的實例。
62. 如何實現(xiàn)對象克???
有兩種方式:
1). 實現(xiàn)Cloneable接口并重寫Object類中的clone()方法;
2). 實現(xiàn)Serializable接口,通過對象的序列化和反序列化實現(xiàn)克隆,可以實現(xiàn)真正的深度克隆,代碼如下:
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(); // 說明:調(diào)用ByteArrayInputStream或ByteArrayOutputStream對象的close方法沒有任何意義 // 這兩個基于內(nèi)存的流只要垃圾回收器清理對象就能夠釋放資源,這一點不同于對外部資源(如文件流)的釋放 } }
下面是測試代碼:
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; } @Override public 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; } @Override public 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關(guān)聯(lián)的汽車對象的品牌屬性 // 原來的Person對象p1關(guān)聯(lián)的汽車不會受到任何影響 // 因為在克隆Person對象時其關(guān)聯(lián)的汽車對象也被克隆了 System.out.println(p1); } catch (Exception e) { e.printStackTrace(); } } }
注意:基于序列化和反序列化實現(xiàn)的克隆不僅僅是深度克隆,更重要的是通過泛型限定,可以檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優(yōu)于使用Object類的clone方法克隆對象。讓問題在編譯的時候暴露出來總是好過把問題留到運行時。
63. 深拷貝和淺拷貝區(qū)別是什么?
淺拷貝只是復制了對象的引用地址,兩個對象指向同一個內(nèi)存地址,所以修改其中任意的值,另一個值都會隨之變化,這就是淺拷貝(例:assign())深拷貝是將對象及值復制過來,兩個對象修改其中任意的值另一個值不會改變,這就是深拷貝(例:JSON.parse()和JSON.stringify(),但是此方法無法復制函數(shù)類型)尚硅谷Java大廠面試題第3季,跳槽必刷題目+必掃技術(shù)盲點(周陽主講)_嗶哩嗶哩_bilibili
https://www.bilibili.com/video/BV1Hy4y1B78T?
尚硅谷Java大廠面試題第2季,面試必刷,跳槽大廠神器_嗶哩嗶哩_bilibili
https://www.bilibili.com/video/BV18b411M7xz?
尚硅谷經(jīng)典Java面試題第一季(java面試精講)_嗶哩嗶哩_bilibili
https://www.bilibili.com/video/BV1Eb411P7bP?
強烈推薦一個網(wǎng)站:
Java面試題-幫助你通過Java面試 (zwmst.com)
https://zwmst.com/
Java后端開發(fā)是Java語言最主要的應用領(lǐng)域之一,由于Java語言具有穩(wěn)定的性能表現(xiàn)和較強的擴展性,所以Java語言通常是不少大型互聯(lián)網(wǎng)平臺的后端服務解決方案。
Java語言進行后端開發(fā)通常需要掌握以下知識結(jié)構(gòu):
第一:Java分布式開發(fā)相關(guān)技術(shù)。采用Java語言實現(xiàn)后端服務開發(fā)通常需要采用分布式框架,所以掌握Java分布式開發(fā)技術(shù)對于后端開發(fā)工程師來說是比較重要的。Java分布式開發(fā)需要注意三方面,分別是協(xié)議的選擇、接口的設計和功能的實現(xiàn),在功能實現(xiàn)上可以借助現(xiàn)有的各種框架,在大數(shù)據(jù)時代借助大數(shù)據(jù)平臺來完成分布式開發(fā)也是一個比較明顯的發(fā)展趨勢。
第二:資源的整合。后端服務開發(fā)往往需要整合多方面的資源,包括數(shù)據(jù)庫資源、各種第三方服務資源(人臉識別、語音識別)等,另外對于一些業(yè)務流程比較復雜的后端開發(fā)來說,通常還需要結(jié)合一些工作流框架來完成具體任務的開發(fā)。在當前的云計算環(huán)境下,資源整合已經(jīng)變得越來越簡單了,但是如何通過編程來實現(xiàn)這些資源的整合也需要一個實踐的過程。
第三:模塊化編程能力。Java的模塊化開發(fā)一直是后端開發(fā)的重點,由于Java語言在模塊化方面長期存在一定的不足,所以早期通常都是通過采用像OSGI類似的解決方案,雖然在JDK9之后Java語言提供了模塊化支持,但是模塊化依然是后端開發(fā)人員需要重點掌握的能力。
最后,在面試的過程中要注重強調(diào)自己的開發(fā)經(jīng)驗,開發(fā)經(jīng)驗往往是面試官比較關(guān)注的重點內(nèi)容,通過項目經(jīng)驗往往能夠比較直觀的呈現(xiàn)出自己的工作能力和知識結(jié)構(gòu),通常來說一定要說一些比較有代表性的項目,一般來說項目規(guī)模越大越好,自己的角色越重要越好。
我從事互聯(lián)網(wǎng)行業(yè)多年,目前也在帶計算機專業(yè)的研究生,主要的研究方向集中在大數(shù)據(jù)和人工智能領(lǐng)域,我會陸續(xù)寫一些關(guān)于互聯(lián)網(wǎng)技術(shù)方面的文章,感興趣的朋友可以關(guān)注我,相信一定會有所收獲。
如果有互聯(lián)網(wǎng)方面的問題,也可以咨詢我,謝謝!
做java后端開發(fā)個人應當注意的應該還是自己過硬的技術(shù)吧,有著過硬的技術(shù)在面試時你才能更加的有底氣個自信,但這些也需要你在某些方面有自己獨到的見解,使你個別人拉開差距,如你需要熟練的掌握各種類庫,熟悉各類框架像Spring和Mybatis這些,同時在后端開發(fā)是比較重要的數(shù)據(jù)庫知識能夠熟練的運用,有一定的大數(shù)據(jù)經(jīng)驗就更好啦。
在面試前你還應當,快速的將你掌握的技術(shù),進行整合,以便你在面試時能夠簡潔快速的介紹它們。
同時你也應當具備一定的網(wǎng)絡安全知識,這在后端開發(fā)尤為重要,可能會被面試官著重提問,所以你也應該在面試前對其有一定的見解。
最后就是個人的素質(zhì)能力,如團隊協(xié)作,團隊溝通等的,也尤為重要,可以準備一些做過的團隊項目的資料和總結(jié)等。