面試題帶答案
Java基礎篇
1. JDK 和 JRE 的區別是什麼?
- JDK:Java Development Kit 的簡稱,java 開發工具包,提供了 java 的開發環境和執行環境。
- JRE:Java Runtime Environment 的簡稱,java 執行環境,為 java 的執行提供了所需環境。
總結:JDK 包含了 JRE,同時還包含了編譯 java 原始碼的編譯器 javac,還包含了很多 java 程式除錯和分析的工具。簡單來說:如果你需要執行 java 程式,只需安裝 JRE 就可以了,如果你需要編寫 java 程式,就需要安裝 JDK。
2. Java中有哪些資料型別?
Java中資料型別分為基本資料型別和引用資料型別2種
- 基本型別:byte(預設值0,佔1位元組)、short(預設值0,佔2位元組)、int(預設值0,佔4位元組)、long(預設值0,佔8位元組)、float(預設值0.0,佔4位元組)、double(預設值0.0,佔8位元組)、char(預設值\u0000,佔2位元組)、boolean(預設值false)
- 引用型別:類(預設值null)、介面(預設值null)、陣列(預設值null)
3. == 和 equals 的區別是什麼?
基本型別和引用型別比較,== 的作用效果是不同的。
- 基本型別:比較的是值是否相同
- 引用型別:比較的是引用是否相同
int x = 10; int y = 10; String a = "panda"; String b = "panda"; String c = new String("panda"); // true 基本型別比較值是否相同 System.out.println(x == y); // true 引用型別比較引用是否相同,這裡引用相同 System.out.println(a == b); // false 引用不同 System.out.println(a == c); // true 引用不同,String重寫了equals,使其用值比較 System.out.println(a.equals(c));
equals 本質上就是 ==,Object類中定義的 equals 方法如下
public boolean equals(Object obj) {
return (this == obj);
}
總結:== 對於基本型別比較的是值,對於引用型別比較的是引用;而 equals 預設情況下是引用比較,只是很多類重寫了 equals 方法,
比如 String、Integer 等把它變成了值比較,所以一般情況下 equals 比較的是值是否相等。
4. 兩個物件的 hashCode() 相同,則 equals() 也一定為 true 正確嗎?
不正確,兩個物件的 hashCode() 相同,equals() 不一定 true。比如在 map 中,hashCode() 相等,只能說明這兩個鍵值對的雜湊值相同,不代表這兩個鍵值對相等。
String str1 = "通話";
String str2 = "重地";
// str1: 1179395 | str2: 1179395
System.out.println(String.format("str1: %d | str2: %d",str1.hashCode(),str2.hashCode()));
// false
System.out.println(str1.equals(str2));
5. Java 中 final 關鍵字的作用是什麼?
- final 修飾的類叫最終類,不能被繼承
- final 修飾的方法叫最終方法,不能被重寫,但可以被繼承
- final 修飾的變數叫常量,必須初始化,初始化之後值不能被修改
6. String、StringBuffer、StringBuilder 的區別是什麼?
String 是字串常量,每次操作都會生產新的物件,適用於少量字串操作的情況;StringBuffer、StringBuilder 是字串變數,StringBuffer 是執行緒安全的,而 StringBuilder 是非執行緒安全的,但 StringBuilder 的效能卻高於 StringBuffer,所以在單執行緒環境下推薦使用 StringBuilder,多執行緒環境下推薦使用 StringBuffer。
7. String str=“donkey” 與 String str=new String(“donkey”) 一樣嗎?
不一樣,因為記憶體的分配方式不一樣。String str=“donkey”,java 虛擬機器會將其分配到常量池中;而 String str=new String(“donkey”) 則會被分到堆記憶體中。
8. 字串如何反轉?
使用 StringBuilder 或者 stringBuffer 的 reverse() 方法
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("abcdefg");
System.out.println(stringBuffer.reverse()); // gfedcba
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abcdefg");
System.out.println(stringBuilder.reverse()); // gfedcba
9. String 類中常用方法都有哪些?
String str = " app le ";
// indexOf(): 返回指定字元的索引
System.out.println(str.indexOf("a")); // 1
// charAt(): 返回指定索引處的字元
System.out.println(str.charAt(5)); // l
// replace(): 字串替換
System.out.println(str.replace("pp", "cc")); // " acc le "
// trim(): 去除字串兩端空白
System.out.println(str.trim()); // "app le"
// split(): 分割字串,返回一個分割後的字串陣列
String[] arr = str.split(" ");
// getBytes(): 返回字串的 byte 型別陣列
byte[] bytes = str.getBytes();
// length(): 返回字串長度
System.out.println(str.length()); // 8
// toLowerCase(): 將字串轉成小寫字母
System.out.println(str.toLowerCase()); // " app le "
// toUpperCase(): 將字串轉成大寫字元
System.out.println(str.toUpperCase()); // " APP LE "
// substring(): 擷取字串
System.out.println(str.substring(2)); // "pp le "
// equals(): 字串比較
System.out.println("apple".equals(str)); // false
10. 抽象類是什麼?
擁有抽象方法(指沒有方法體的方法,同時抽象方法還必須使用關鍵字abstract 做修飾)的類就是抽象類,抽象類要使用 abstract 關鍵字宣告。
11. 抽象類必須要有抽象方法嗎?
不需要,抽象類不一定非要有抽象方法,如下程式碼可以正常執行
public abstract class elephant { String str = "apple"; public void test01(){ System.out.println("aaaa"); } }
12. 普通類和抽象類的區別是什麼?
- 普通類不能包含抽象方法,抽象類可以有抽象方法
- 普通類可以直接例項化,抽象類不能直接例項化
13. 抽象類能使用 final 修飾嗎?
不能,定義抽象類就是讓其他類繼承的,如果定義為 final 該類就不能被繼承,這樣彼此就會產生矛盾,所以 final 不能修飾抽象類。
14. 介面是什麼?
介面可以理解為一種特殊的類,裡面全部是由全域性常量和公共的抽象方法所組成。介面是解決Java無法使用多繼承的一種手段,但是介面在實際中更多的作用是制定標準。
15. 介面和抽象類的區別是什麼?
- 介面必須使用 implements 來實現介面;抽象類的子類使用 extends 來繼承
- 介面不能有建構函式;抽象類可以有建構函式
- 一個類只能繼承一個抽象類,但可以實現多個介面
- 抽象類中成員變數預設 default,可在子類中被重新定義,也可被重新賦值,抽象方法被 abstract 修飾,不能被 private、static、synchronized 和 native 等修飾;介面中成員變數預設為 public static final 修飾,必須賦初值,不能被修改,其所有的成員方法預設使用 public abstract 修飾的
16. Java 中 IO 流分幾種?
- 按功能分:輸入流(input)、輸出流(output)
- 按型別分:位元組流、字元流
位元組流和字元流的區別:位元組流按 8 位傳輸,以位元組為單位輸入輸出資料;字元流按 16 位傳輸,以字元為單位輸入輸出資料。
17. BIO、NIO、AIO 有什麼區別?
- BIO:同步阻塞 IO,就是我們平常使用的傳統 IO,伺服器的實現模式是一個請求連線一個執行緒,併發處理能力低,可能造成不必要的執行緒開銷,嚴重的還將導致伺服器記憶體溢位。
- NIO:同步非阻塞 IO,是傳統 IO 的升級,伺服器的實現模式是多個請求一個執行緒,即請求會註冊到多路複用器Selector上,多路複用器輪詢到連線有IO請求時才啟動一個執行緒處理。
- AIO:非同步非阻塞 IO,是 NIO 的升級,也叫 NIO2,伺服器的實現模式為多個有效請求一個執行緒,客戶端的IO請求都是由OS先完成再通知伺服器應用去啟動執行緒處理(回撥)。
18. Files 的常用方法都有哪些?
// Files.exists():檢測檔案路徑是否存在
Path path1 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test");
System.out.println(Files.exists(path1, new LinkOption[]{LinkOption.NOFOLLOW_LINKS})); // true
// Files.createFile():建立檔案
Path path2 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\a.txt");
try {
Path newFilw = Files.createFile(path2);
} catch (FileAlreadyExistsException e){
System.out.println("exists");
} catch (IOException e) {
System.out.println("other wrong");
}
// Files.createDirectory():建立資料夾
Path path3 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\newDirectory");
try {
Path newDirectory = Files.createDirectory(path3);
} catch (FileAlreadyExistsException e) {
System.out.println("exists");
} catch (IOException e){
System.out.println("other wrong");
}
// Files.delete():刪除一個檔案或目錄
try {
Files.delete(path2);
} catch (IOException e) {
e.printStackTrace();
}
// Files.copy():複製檔案
Path source = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\b.txt");
Path target = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\newb.txt");
try {
Files.copy(source,target);
} catch (FileAlreadyExistsException e) {
System.out.println("targetFile already exists");
} catch (IOException e){
System.out.println("other wrong");
}
// Files.move():移動檔案
Path source1 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\b.txt");
Path target1 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\newDirectory\\a.txt");
try {
Files.move(source1,target1);
} catch (FileAlreadyExistsException e) {
System.out.println("targetFile1 already exists");
} catch (IOException e){
System.out.println("other wrong");
}
// Files.size():檢視檔案個數
// Files.read():讀取檔案
// Files.write():寫入檔案
Java 容器篇
1. Java 中都有哪些容器?
2. Collection 和 Collections 的區別是什麼?
- java.util.Collection 是一個集合介面(集合類的一個頂級介面)。它提供了對集合物件進行基本操作的通用介面方法。Collection 介面在 Java 類庫中有很多具體的實現。Collection 介面的意義是為各種具體的集合提供了最大化的統一操作方式,其直接繼承介面有 List 與 Set。
- Collections 則是集合類的一個工具類,其中提供了一系列靜態方法,用於對集合中元素進行排序、搜尋以及執行緒安全等各種操作。
3. List、Set、Map 之間的區別是什麼?
4. HashMap 和 Hashtable 的區別是什麼?
- hashMap 去除了 HashTable 的 contains() 方法,但是加上了 containsValue() 和 containsKey() 方法。
- hashTable 是執行緒同步的,而 HashMap 是非同步的,HashMap 效率上比 hashTable 要高。
- hashMap 允許空鍵空值,而 hashTable 不允許。
5. 如何決定使用 HashMap 還是 TreeMap?
在 Map 中做插入、刪除和定位元素這類操作,HashMap 是最好的選擇。假如你需要對一個有序的 key 集合進行遍歷,TreeMap 是更好的選擇。
6. HashMap 的實現原理是什麼?
HashMap概述: HashMap 是基於雜湊表的Map介面的非同步實現。此實現提供所有可選的對映操作,並允許使用 null 值和 null 鍵。此類不保證對映的順序,特別是它不保證該順序恆久不變。
HashMap的資料結構: HashMap 實際上是一個“連結串列雜湊”的資料結構,即陣列和連結串列的結合體。
當我們往 HashMap 中 put 元素時,首先根據 key 的 hashcode 重新計算 hash 值,根據 hash 值得到這個元素在陣列中的位置(下標),如果該陣列在該位置上已經存放了其他元素,那麼在這個位置上的元素將以連結串列的形式存放,新加入的放在鏈頭,最先加入的放入鏈尾。如果陣列中該位置沒有元素,就直接將該元素放到陣列的該位置上。
Jdk1.8 中對 HashMap 的實現做了優化,當連結串列中的節點資料超過八個之後,該連結串列會轉為紅黑樹來提高查詢效率,從原來的 O(n) 到 O(logn)。
7. HashSet 的實現原理是什麼?
HashSet 實現 Set 介面,由雜湊表(實際上是一個 HashMap 例項)支援。它不保證 set 的迭代順序;特別是它不保證該順序恆久不變;此類允許使用 null 元素;HashSet 中不允許有重複元素。
HashSet 是基於 HashMap 實現的,HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 中的值都是統一的一個private static final Object PRESENT = new Object()
。HashSet跟HashMap一樣,都是一個存放連結串列的陣列。
8. ArrayList 和 LinkedList 的區別是什麼?
ArrayList 底層基於動態陣列,隨機訪問元素效率高,向集合尾部新增元素效率高,刪除或者在其他位置新增元素效率低(需要移動陣列);LinkedList 基於連結串列的動態陣列,資料新增和刪除效率高,只需要改變指標指向即可,但是訪問資料的平均效率低,需要對連結串列進行遍歷。
9. 如何實現陣列和 List 之間的轉換?
10. ArrayList 和 Vector 的區別是什麼?
- Vector 是同步的,執行緒安全,而 ArrayList 不是。因此,ArrayList 的效能要高於Vector,如果你想在迭代的時候對列表進行改變,應該使用 CopyOnWriteArrayList。
- 超過初始容量時,ArrayList 擴容1.5倍,Vector 擴容2倍。
11. Array 和 ArrayList 的區別是什麼?
- Array 可以容納基本型別和物件,而 ArrayList 只能容納物件。
- Array 是指定大小的,而 ArrayList 初始化大小是固定的。
- Array 沒有提供 ArrayList 那麼多功能,比如addAll、removeAll和iterator等。
12. 在 Queue 中 poll() 和 remove() 的區別是什麼?
Queue 中 remove() 和 poll() 都是用來從佇列頭部刪除一個元素,在佇列元素為空的情況下,remove() 方法會丟擲 NoSuchElementException 異常,poll() 方法只會返回 null。
13. 執行緒安全的集合類有哪些?
- vector:就比 ArrayList 多了個同步化機制(執行緒安全),因為效率較低,現在已經不太建議使用
- statck:堆疊類,先進後出
- hashtable:就比 HashMap 多了個執行緒安全
- enumeration:列舉,相當於迭代器
14. 迭代器 Iterator 是什麼?
迭代器是一種設計模式,它是一個物件,它可以遍歷並選擇序列中的物件,而開發人員不需要了解該序列的底層結構。迭代器通常被稱為“輕量級”物件,因為建立它的代價小。
15. Iterator 怎麼使用?有什麼特點?
- Iterator 功能比較簡單,並且只能單向移動
- 使用方法 iterator() 要求容器返回一個 Iterator。第一次呼叫 Iterator 的 next() 方法時,它返回序列的第一個元素。注意:iterator() 方法是 java.lang.Iterable 介面,被 Collection 繼承。
- 使用 hasNext() 檢查序列中是否還有元素
- 使用 next() 獲得序列中的下一個元素
- 使用 remove() 將迭代器新返回的元素刪除
- Iterator 是 Java 迭代器最簡單的實現,為 List 設計的 ListIterator 具有更多的功能,它可以從兩個方向遍歷 List,也可以從 List 中插入和刪除元素。
4. HashMap 和 Hashtable 的區別是什麼?
- hashMap 去除了 HashTable 的 contains() 方法,但是加上了 containsValue() 和 containsKey() 方法。
- hashTable 是執行緒同步的,而 HashMap 是非同步的,HashMap 效率上比 hashTable 要高。
- hashMap 允許空鍵空值,而 hashTable 不允許。
5. 如何決定使用 HashMap 還是 TreeMap?
在 Map 中做插入、刪除和定位元素這類操作,HashMap 是最好的選擇。假如你需要對一個有序的 key 集合進行遍歷,TreeMap 是更好的選擇。
6. HashMap 的實現原理是什麼?
HashMap概述: HashMap 是基於雜湊表的Map介面的非同步實現。此實現提供所有可選的對映操作,並允許使用 null 值和 null 鍵。此類不保證對映的順序,特別是它不保證該順序恆久不變。
HashMap的資料結構: HashMap 實際上是一個“連結串列雜湊”的資料結構,即陣列和連結串列的結合體。
當我們往 HashMap 中 put 元素時,首先根據 key 的 hashcode 重新計算 hash 值,根據 hash 值得到這個元素在陣列中的位置(下標),如果該陣列在該位置上已經存放了其他元素,那麼在這個位置上的元素將以連結串列的形式存放,新加入的放在鏈頭,最先加入的放入鏈尾。如果陣列中該位置沒有元素,就直接將該元素放到陣列的該位置上。
Jdk1.8 中對 HashMap 的實現做了優化,當連結串列中的節點資料超過八個之後,該連結串列會轉為紅黑樹來提高查詢效率,從原來的 O(n) 到 O(logn)。
7. HashSet 的實現原理是什麼?
HashSet 實現 Set 介面,由雜湊表(實際上是一個 HashMap 例項)支援。它不保證 set 的迭代順序;特別是它不保證該順序恆久不變;此類允許使用 null 元素;HashSet 中不允許有重複元素。
HashSet 是基於 HashMap 實現的,HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 中的值都是統一的一個private static final Object PRESENT = new Object()
。HashSet跟HashMap一樣,都是一個存放連結串列的陣列。
8. ArrayList 和 LinkedList 的區別是什麼?
ArrayList 底層基於動態陣列,隨機訪問元素效率高,向集合尾部新增元素效率高,刪除或者在其他位置新增元素效率低(需要移動陣列);LinkedList 基於連結串列的動態陣列,資料新增和刪除效率高,只需要改變指標指向即可,但是訪問資料的平均效率低,需要對連結串列進行遍歷。
9. 如何實現陣列和 List 之間的轉換?
10. ArrayList 和 Vector 的區別是什麼?
- Vector 是同步的,執行緒安全,而 ArrayList 不是。因此,ArrayList 的效能要高於Vector,如果你想在迭代的時候對列表進行改變,應該使用 CopyOnWriteArrayList。
- 超過初始容量時,ArrayList 擴容1.5倍,Vector 擴容2倍。
11. Array 和 ArrayList 的區別是什麼?
- Array 可以容納基本型別和物件,而 ArrayList 只能容納物件。
- Array 是指定大小的,而 ArrayList 初始化大小是固定的。
- Array 沒有提供 ArrayList 那麼多功能,比如addAll、removeAll和iterator等。
12. 在 Queue 中 poll() 和 remove() 的區別是什麼?
Queue 中 remove() 和 poll() 都是用來從佇列頭部刪除一個元素,在佇列元素為空的情況下,remove() 方法會丟擲 NoSuchElementException 異常,poll() 方法只會返回 null。
13. 執行緒安全的集合類有哪些?
- vector:就比 ArrayList 多了個同步化機制(執行緒安全),因為效率較低,現在已經不太建議使用
- statck:堆疊類,先進後出
- hashtable:就比 HashMap 多了個執行緒安全
- enumeration:列舉,相當於迭代器
14. 迭代器 Iterator 是什麼?
迭代器是一種設計模式,它是一個物件,它可以遍歷並選擇序列中的物件,而開發人員不需要了解該序列的底層結構。迭代器通常被稱為“輕量級”物件,因為建立它的代價小。
15. Iterator 怎麼使用?有什麼特點?
- Iterator 功能比較簡單,並且只能單向移動
- 使用方法 iterator() 要求容器返回一個 Iterator。第一次呼叫 Iterator 的 next() 方法時,它返回序列的第一個元素。注意:iterator() 方法是 java.lang.Iterable 介面,被 Collection 繼承。
- 使用 hasNext() 檢查序列中是否還有元素
- 使用 next() 獲得序列中的下一個元素
- 使用 remove() 將迭代器新返回的元素刪除
- Iterator 是 Java 迭代器最簡單的實現,為 List 設計的 ListIterator 具有更多的功能,它可以從兩個方向遍歷 List,也可以從 List 中插入和刪除元素。
public static void main(String[] args) {
// List
ArrayList<String> list = new ArrayList<>();
list.add("apple");
list.add("pear");
list.add("banana");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String s = iterator.next();
if ("apple".equals(s)){
iterator.remove();
}
}
list.forEach(item -> System.out.println(item));
// Map<key,value>
Map<String,String> map=new HashMap<>();
map.put("pig","豬");
map.put("cat","貓");
map.put("dog","狗");
Iterator<String> iterator1 = map.keySet().iterator();
Iterator<String> iterator2 = map.values().iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
while (iterator2.hasNext()){
System.out.println(iterator2.next());
}
}
16. Iterator 和 ListIterator 的區別是什麼?
- Iterator 可用來遍歷 Set 和 List 集合,但是 ListIterator 只能用來遍歷 List。
- Iterator 對集合只能是前向遍歷,ListIterator 既可以前向也可以後向。
- ListIterator 實現了 Iterator 介面,幷包含其他的功能,比如:增加元素,替換元素,獲取前一個和後一個元素的索引等等。
Java多執行緒
1. 並行和併發的區別是什麼?
- 並行是指兩個或者多個事件在同一時刻發生;而併發是指兩個或多個事件在同一時間間隔發生
- 並行是在不同實體上的多個事件,併發是在同一實體上的多個事件
- 在一臺處理器上“同時”處理多個任務,在多臺處理器上同時處理多個任務。如 hadoop 分散式叢集
普通解釋:
- 併發:交替做不同事情的能力
- 並行:同時做不同事情的能力
專業術語:
- 併發:不同的程式碼塊交替執行
- 並行:不同的程式碼塊同時執行
2. 執行緒和程序的區別是什麼?
程序是程式執行和資源分配的基本單位,一個程式至少有一個程序,一個程序至少有一個執行緒。程序在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體資源,減少切換次數,從而效率更高。執行緒是程序的一個實體,是 cpu 排程和分派的基本單位,是比程式更小的能獨立執行的基本單位。同一程序中的多個執行緒之間可以併發執行。
3. 守護執行緒是什麼?
守護執行緒(即 daemon thread),是個服務執行緒,準確地來說就是服務其他的執行緒。它能夠自我結束。如果 JVM 中沒有一個正在執行的非守護執行緒,這個時候,JVM 會退出。JVM 中的垃圾回收執行緒就是典型的守護執行緒,如果說沒有守護執行緒,JVM 就永遠不會退出了
4. 建立執行緒有哪幾種方式?
① 繼承 Thread 類建立執行緒
② 實現 Runnable 介面建立執行緒
③ 通過 Callable 和 Future 建立執行緒
public class MyThread implements Callable {
@Override
public Object call() {
System.out.println("run!");
return "run task success";
}
}
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask<String>(myThread);
Thread thread = new Thread(futureTask);
thread.start();
// 獲得子執行緒執行結束後的返回值
System.out.println(futureTask.get()); // run task success
}
}
5. 說一下 runnable 和 callable 有什麼區別?
- Runnable 介面中的 run() 方法的返回值是 void,它做的事情只是純粹地去執行run()方法中的程式碼
- Callable 介面中的 call() 方法是有返回值的,是一個泛型,和 Future、FutureTask 配合可以用來獲取非同步執行的結果
6. 執行緒有哪些狀態?
執行緒通常都有五種狀態,建立、就緒、執行、阻塞和死亡。
- 建立狀態:在生成執行緒物件,並沒有呼叫該物件的start方法,這時執行緒處於建立狀態。
- 就緒狀態:當呼叫了執行緒物件的 start 方法之後,該執行緒就進入了就緒狀態,但是此時執行緒排程程式還沒有把該執行緒設定為當前執行緒,此時處於就緒狀態。線上程執行之後,從等待或者睡眠中回來之後,也會處於就緒狀態。
- 執行狀態:執行緒排程程式將處於就緒狀態的執行緒設定為當前執行緒,此時執行緒就進入了執行狀態,開始執行 run 函式當中的程式碼。
- 阻塞狀態:執行緒正在執行的時候,被暫停,通常是為了等待某個事件的發生(比如說某項資源就緒)之後再繼續執行。sleep,suspend,wait 等方法都可以導致執行緒阻塞。
- 死亡狀態:如果一個執行緒的 run 方法執行結束或者呼叫 stop 方法後,該執行緒就會死亡。對於已經死亡的執行緒,無法再使用 start 方法令其進入就緒。
7. sleep() 和 wait() 有什麼區別?
- sleep():是執行緒類(Thread)的靜態方法,讓呼叫執行緒進入睡眠狀態,讓出執行機會給其他執行緒,等到休眠時間結束後,執行緒進入就緒狀態和其他執行緒一起競爭cpu的執行時間。因為 sleep() 是 static 靜態的方法,他不能改變物件的鎖,當一個 synchronized 塊中呼叫了 sleep() 方法,執行緒雖然進入休眠,但是物件的鎖沒有被釋放,其他執行緒依然無法訪問這個物件。
- wait():是 Object 類的方法,當一個執行緒執行到 wait 方法時,它就進入到一個和該物件相關的等待池,同時釋放物件的鎖,使其他執行緒能夠訪問,可以通過 notify,notifyAll 方法來喚醒等待的執行緒。
8. notify() 和 notifyAll() 有什麼區別?
如果執行緒呼叫了物件的 wait() 方法,那麼執行緒便會處於該物件的等待池中,等待池中的執行緒不會去競爭該物件的鎖。
當有執行緒呼叫了物件的 notifyAll() 方法(喚醒所有 wait 執行緒)或 notify() 方法(只隨機喚醒一個 wait 執行緒),被喚醒的的執行緒便會進入該物件的鎖池中,鎖池中的執行緒會去競爭該物件鎖。也就是說,呼叫了 notify 後只有一個執行緒會由等待池進入鎖池,而 notifyAll 會將該物件等待池內的所有執行緒移動到鎖池中,等待鎖競爭。
優先順序高的執行緒競爭到物件鎖的概率大,假若某執行緒沒有競爭到該物件鎖,它還會留在鎖池中,唯有執行緒再次呼叫 wait() 方法,它才會重新回到等待池中。而競爭到物件鎖的執行緒則繼續往下執行,直到執行完了 synchronized 程式碼塊,它會釋放掉該物件鎖,這時鎖池中的執行緒會繼續競爭該物件鎖。
9. run() 和 start() 有什麼區別?
每個執行緒都是通過某個特定 Thread 物件所對應的方法 run() 來完成其操作的,方法 run() 稱為執行緒體。通過呼叫 Thread 類的 start() 方法來啟動一個執行緒。
- start() 方法來啟動一個執行緒,真正實現了多執行緒執行。這時無需等待 run 方法體程式碼執行完畢,可以直接繼續執行下面的程式碼; 這時此執行緒是處於就緒狀態, 並沒有執行。 然後通過此 Thread 類呼叫方法 run() 來完成其執行狀態, 這裡方法 run() 稱為執行緒體,它包含了要執行的這個執行緒的內容,run 方法執行結束, 此執行緒終止。然後CPU再排程其它執行緒。
- run() 方法是在本執行緒裡的,只是執行緒裡的一個函式,而不是多執行緒的。 如果直接呼叫 run(),其實就相當於是呼叫了一個普通函式而已,直接呼叫 run() 方法必須等待 run() 方法執行完畢才能執行下面的程式碼,所以執行路徑還是隻有一條,根本就沒有執行緒的特徵,所以在多執行緒執行時要使用 start() 方法而不是 run() 方法。
10. 建立執行緒池有哪幾種方式?
- newFixedThreadPool(int nThreads):建立一個固定長度的執行緒池,每當提交一個任務就建立一個執行緒,直到達到執行緒池的最大數量,這時執行緒規模將不再變化,當執行緒發生未預期的錯誤而結束時,執行緒池會補充一個新的執行緒。
- newCachedThreadPool():建立一個可快取的執行緒池,如果執行緒池的規模超過了處理需求,將自動回收空閒執行緒,而當需求增加時,則可以自動新增新執行緒,執行緒池的規模不存在任何限制。
- newSingleThreadExecutor():這是一個單執行緒的 Executor,它建立單個工作執行緒來執行任務,如果這個執行緒異常結束,會建立一個新的來替代它;它的特點是能確保依照任務在佇列中的順序來序列執行。
- newScheduledThreadPool(int corePoolSize):建立一個固定長度的執行緒池,而且以延遲或定時的方式來執行任務,類似於Timer。
11. 執行緒池都有哪些狀態?
執行緒池有5種狀態:Running、ShutDown、Stop、Tidying、Terminated。
- Running:這是最正常的狀態,接受新的任務,處理等待佇列中的任務。執行緒池的初始化狀態是Running。執行緒池被一旦被建立,就處於 Running 狀態,並且執行緒池中的任務數為 0
- ShutDown:不接受新的任務提交,但是會繼續處理等待佇列中的任務。呼叫執行緒池的 shutdown() 方法時,執行緒池由Running -> ShutDown。
- Stop:不接受新的任務提交,不再處理等待佇列中的任務,中斷正在執行任務的執行緒。呼叫執行緒池的shutdownNow() 方法時,執行緒池由 (Running or ShutDown) -> Stop。
- Tidying:所有的任務都銷燬了,workCount 為 0,執行緒池的狀態在轉換為 Tidying 狀態時,會執行鉤子方法 terminated()。因為 terminated() 在 ThreadPoolExecutor 類中是空的,所以使用者想線上程池變為 Tidying 時進行相應的處理;可以通過過載 terminated() 函式來實現。 當執行緒池在 ShutDown 狀態下,阻塞佇列為空並且執行緒池中執行的任務也為空時,就會由 ShutDown -> Tidying。當執行緒池在Stop 狀態下,執行緒池中執行的任務為空時,就會由Stop -> Tidying。
- Terminated:執行緒池處在 Tidying 狀態時,執行完 terminated() 之後,就會由 Tidying -> Terminated。
執行緒池各個狀態切換框架圖:
12. 執行緒池中 submit() 和 execute() 方法有什麼區別?
- 接收的引數不一樣
submit(Runnable task)
submit(Runnable task,T result)
submit(Callable task)
execute(Runnable command)
- submit() 有返回值,而 execute() 沒有。用到返回值的例子,比如說我有很多個做 validation 的task,我希望所有的 task 執行完,然後每個 task 告訴我它的執行結果,是成功還是失敗,如果是失敗,原因是什麼。
- submit()方便 Exception 處理,如果你在你的 task 裡會丟擲 checked 或者 unchecked exception,而你又希望外面的呼叫者能夠感知這些 exception 並做出及時的處理,那麼就需要用到submit,通過捕獲 Future.get 丟擲的異常。
13. java 程式中怎麼保證多執行緒的執行安全?
執行緒安全在三個方面體現:
- 原子性:提供互斥訪問,同一時刻只能有一個執行緒對資料進行操作(atomic,synchronized)
- 可見性:一個執行緒對主記憶體的修改可以及時地被其他執行緒看到(synchronized,volatile)
- 有序性:一個執行緒觀察其他執行緒中的指令執行順序,由於指令重排序,該觀察結果一般雜亂無序(happens-before 原則)
14. 多執行緒鎖的升級原理是什麼?
在 Java 中,鎖共有 4 種狀態,級別從低到高依次為:無狀態鎖,偏向鎖,輕量級鎖和重量級鎖狀態,這幾個狀態會隨著競爭情況逐漸升級。鎖可以升級但不能降級。
鎖升級的圖示過程:
15. 什麼是死鎖?
死鎖是指兩個或兩個以上的程序在執行過程中,由於競爭資源或者由於彼此通訊而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程序稱為死鎖程序。是作業系統層面的一個錯誤,是程序死鎖的簡稱,最早在 1965 年由 Dijkstra 在研究銀行家演算法時提出的,它是計算機作業系統乃至整個併發程式設計領域最難處理的問題之一。
16. 怎麼防止死鎖?
死鎖的四個必要條件:
- 互斥條件:程序對所分配到的資源不允許其他程序進行訪問,若其他程序訪問該資源,只能等待,直至佔有該資源的程序使用完成後釋放該資源
- 請求和保持條件:程序獲得一定的資源之後,又對其他資源發出請求,但是該資源可能被其他程序佔有,此時請求阻塞,但又對自己獲得的資源保持不放
- 不可剝奪條件:是指程序已獲得的資源,在未完成使用之前,不可被剝奪,只能在使用完後自己釋放
- 環路等待條件:是指程序發生死鎖後,若干程序之間形成一種頭尾相接的迴圈等待資源關係
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之 一不滿足,就不會發生死鎖。
理解了死鎖的原因,尤其是產生死鎖的四個必要條件,就可以最大可能地避免、預防和 解除死鎖。所以,在系統設計、程序排程等方面注意如何不讓這四個必要條件成立,如何確 定資源的合理分配演算法,避免程序永久佔據系統資源。此外,也要防止程序在處於等待狀態的情況下佔用資源。因此,對資源的分配要給予合理的規劃。
17. ThreadLocal 是什麼?有哪些使用場景?
執行緒區域性變數是侷限於執行緒內部的變數,屬於執行緒自身所有,不在多個執行緒間共享。Java 提供ThreadLocal 類來支援執行緒區域性變數,是一種實現執行緒安全的方式。但是在管理環境下(如 web 伺服器)使用執行緒區域性變數的時候要特別小心,在這種情況下,工作執行緒的生命週期比任何應用變數的生命週期都要長。任何執行緒區域性變數一旦在工作完成後沒有釋放,Java 應用就存在記憶體洩露的風險。
18. 說一下 synchronized 底層實現原理?
synchronized 可以保證方法或者程式碼塊在執行時,同一時刻只有一個方法可以進入到臨界區,同時它還可以保證共享變數的記憶體可見性。
Java 中每一個物件都可以作為鎖,這是 synchronized 實現同步的基礎:
- 普通同步方法,鎖是當前例項物件
- 靜態同步方法,鎖是當前類的class物件
- 同步方法塊,鎖是括號裡面的物件
19. synchronized 和 volatile 的區別是什麼?
- volatile 本質是在告訴 jvm 當前變數在暫存器(工作記憶體)中的值是不確定的,需要從主存中讀取; synchronized 則是鎖定當前變數,只有當前執行緒可以訪問該變數,其他執行緒被阻塞住。
- volatile 僅能使用在變數級別;synchronized 則可以使用在變數、方法、和類級別的。
- volatile 僅能實現變數的修改可見性,不能保證原子性;而 synchronized 則可以保證變數的修改可見性和原子性。
- volatile 不會造成執行緒的阻塞;synchronized 可能會造成執行緒的阻塞。
- volatile 標記的變數不會被編譯器優化;synchronized 標記的變數可以被編譯器優化。
20. 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 鎖適合程式碼少量的同步問題
21. synchronized 和 ReentrantLock 區別是什麼?
synchronized 是和 if、else、for、while 一樣的關鍵字,ReentrantLock 是類,這是二者的本質區別。既然 ReentrantLock 是類,那麼它就提供了比 synchronized 更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變數,ReentrantLock 比 synchronized 的擴充套件性體現在幾點上:
- ReentrantLock 可以對獲取鎖的等待時間進行設定,這樣就避免了死鎖
- ReentrantLock 可以獲取各種鎖的資訊
- ReentrantLock 可以靈活地實現多路通知
另外,二者的鎖機制其實也是不一樣的:ReentrantLock 底層呼叫的是 Unsafe 的 park 方法加鎖,synchronized 操作的應該是物件頭中 mark word。
22. 說一下 atomic 的原理?
Atomic 包中的類基本的特性就是在多執行緒環境下,當有多個執行緒同時對單個(包括基本型別及引用型別)變數進行操作時,具有排他性,即當多個執行緒同時對該變數的值進行更新時,僅有一個執行緒能成功,而未成功的執行緒可以向自旋鎖一樣,繼續嘗試,一直等到執行成功。
Atomic 系列的類中的核心方法都會呼叫 unsafe 類中的幾個本地方法。我們需要先知道一個東西就是Unsafe 類,全名為:sun.misc.Unsafe,這個類包含了大量的對 C 程式碼的操作,包括很多直接記憶體分配以及原子操作的呼叫,而它之所以標記為非安全的,是告訴你這個裡面大量的方法呼叫都會存在安全隱患,需要小心使用,否則會導致嚴重的後果,例如在通過 unsafe 分配記憶體的時候,如果自己指定某些區域可能會導致一些類似 C++ 一樣的指標越界到其他程序的問題。
String str1 = "通話";String str2 = "重地";// str1: 1179395 | str2: 1179395System.out.println(String.format("str1: %d | str2: %d",str1.hashCode(),str2.hashCode()));// falseSystem.out.println(str1.equals(str2));