前言
梳理了好久,總算是把面試題全部導出來了,畢竟還要上班,這次就給大家總結了一些JAVA開發崗位的經典面試題。
篇幅較大,閱讀過程中可能會有點繁瑣! 但請細細觀看,文章末尾有留給大家的小驚喜!!!
千萬不要錯過了~ 話不多說,咱們就直接開整!
Java開發崗面試題
JavaOOP
Java的數據結構有哪些?
- 線性表(ArrayList)
- 鏈表(LinkedList)
- 棧(Stack)
- 隊列(Queue)
- 圖(Map)
- 樹(Tree)
Java中有幾種數據類型
四型八種
- 整形:byte、short、int、long
- 浮點型:float、double
- 字符型:char
- 布爾型:boolean
String str="aaa",與String str=new String("aaa")一樣嗎?
- 不一樣,第一個字面量聲明字符串,會從常量池里面取,池中沒有則創建,有則復用,后面再同樣聲明一個aaa時,就從池中取出復用。第二個使用new的方式創建,是不會放到常量池中的,所以也不會復用。
String、StringBu??er 和 StringBuilder 的區別是什么?
- String是只讀字符串,它不是基本數據類型,而是一個對象,它的底層是一個final的char字符數組,一經定義,就不能增加和修改,每次對String的操作都是重新生成一個String字符串對象。
- StringBu??er和StringBuilder都繼承了AbstractStringBulder類,2個類都是用來進行字符串操作的。
- StringBu??er是線程安全的,而StringBuilder是非線程安全的,所以StringBuilder效率比StringBu??er高,StringBu??er類的方法大多數都加了synchronized關鍵字。
抽象類和接口的區別是什么?
- 抽象類需要使用abstract關鍵字定義,它可以有抽象方法和實例方法,抽象方法沒有方法體,需要子類實現。包含抽象方法的類,一定是抽象類抽象只能被繼承,不能被實例化
- 接口接口的方法全部都是抽象方法,屬性都是常量接口不能被實例化接口只能被實現,實現接口的類必須實現所有的抽象方法,除非該類是抽象類,可以選擇實現部分抽象方法,剩余了讓子類實現接口可以多繼承
有了equals(),為什么還需要hashCode()
- Java集合有2類,List和Set,List集合有序可重復,而Set集合無序但不可重復。Set集合保證唯一的方法,就是插入時判斷對象的equals()方法,是否和已有元素重復,但是元素較多時,調用equals()方法就會很低效。所以增加了hashCode(),通過元素對象的內存地址計算出一個hash值,比較時,先比較hashCode,hashCode的效率非常高,當元素的hashCode相同時,再調用元素的equals()進行比較,這樣效率就提升了。
介紹Java的強、弱、軟、虛,4種引用
- 強引用,強引用在內存不足時,寧愿發生OOM也不愿意回收它。
- 軟引用,使用SoftReference包裹引用,內存不足時,就會回收。
- 弱引用,使用WeakReference包裹引用,只要JVM垃圾回收發現了它,就會回收。
- 虛引用,回收機制和弱引用差不多,但是它在被回收前,會放入到ReferenceQueue隊列中,并且虛引用聲明時,必須傳ReferenceQueue隊列。因為這個機制,大部分用虛引用來做引用銷毀前的處理工作。
Java創建對象有幾種方式?
有4種:
- new關鍵字
- 通過反射機制
- 通過clone克隆機制
- 通過序列化和反序列化機制
淺拷貝和深拷貝的區別是什么?
例如一個對象中有一個List,淺拷貝和深拷貝效果不同。
- 淺拷貝,只拷貝外層對象,它引用的List并不會拷貝,所以原對象和拷貝對象的List對象是同一個。
- 深拷貝,外層對象拷貝,它所有引用的對象也拷貝,所以拷貝的對象,它引用的List對象是新的一個。
?nal、?nalize()、?nally,它們有什么區別?
- finalfinal關鍵字標記的變量為常量final關鍵字標記的類,不可繼承final關鍵字標記的方法,不可被復寫
- ?nalize?nalize()方法,在Object類上定義,用于對象被回收時,被回調,類似C++中的析構函數,可用于對對象進行銷毀前的處理,但由于GC后再進行特殊處理,可能會出現內存溢出的風險,所以不推薦使用。
- ?nally?nally用于標識代碼塊,和try配合使用,它在return之前執行,無論try代碼塊中是否發生異常,finally代碼塊中的代碼必定會執行。
使用JDBC中,如何防止SQL注入
- 使用PreparedStatement類,而不是使用Statement類。
Java集合、泛型
ArrayList和LinkedList的區別?
- ArrayList底層使用數組,它的查詢使用索引,所以效率高,但是增、刪很慢,因為增、刪都需要重排數組,例如刪除中間的元素,就需要把后面的元素向前挪。
- LinkedList底層使用鏈表,增、刪元素只需要修改元素的前節點和后節點即可,所以效率高,但查詢效率比較差。
HashMap和HashTable的區別
- 繼承關系不同HashMap是繼承自AbstractMap類,而Hashtable是繼承自Dictionary類。
- 對null支持不同Hashtable,key和value都不能為nullHashMap,key可以為null,但是這樣的key只能有一個,而可以多個key的value值為null
- 線程安全Hashtable是線程安全的,它的每個方法都有synchronized 關鍵字,所以多線程環境下可以使用它。HashMap是線程不安全的,但它的效率比Hashtable高,加上大部分場景都是單線程,如果在多線程環境下,推薦使用ConcurrentHashMap,因為它使用了分段鎖,并不對整個數據進行鎖定。
Collection和Collections的不同
- Collection是集合的父接口,子接口有List、Set。
- Collections是集合類的工具類,提供了很多對集合操作的靜態方法,可對集合進行搜索、排序、以及線程安全包裝等。
List、Set、Map,3者的區別
- List,有序,可重復
- Set,無序,不可重復
- Map,無序,鍵值對存儲,Key不可重復,Value可重復
Array和ArrayList有什么區別?
- Array和ArrayList都是用來存儲數據,ArrayList的底層就是使用Array實現的,但它對Array的擴容和功能進行了拓展。
說說List接口和它的實現類
- List接口是Collection接口的子接口。List接口的實現類都是有序,可重復的。List接口的實現類有ArrayList、Vector和LinkedList。
- 實現類ArrayList,底層通過數組實現,它的特點是支持索引查找,所以支持對元素的隨機訪問。缺點是增、刪元素時,需要對數組進行復制、移動,代價比較高,所以它適合隨機訪問,不適合插入和刪除。Vector,底層和ArrayList一樣是通過數組實現,但它的方法都加了同步鎖,所以它可以在多線程環境下訪問,避免同一時段對集合并發讀寫引起不一致性問題,但同步需要性能開銷,所以它的效率比ArrayList低。LinkedList,底層使用鏈表實現,很適合元素的動態插入和刪除,但隨機訪問和遍歷效率會比較低,另外它實現了Deque接口,所以擁有操作頭元素、尾元素的方法,所以可以用來當做棧和隊列使用。
說說Set接口和它的實現類
- Set接口,也是Collection接口的子接口,Set接口的實現類都是不可重復的。Set接口的實現類有HashSet、TreeSet、LinkedHashSet。Set集合不可重復的特性是通過調用元素的hashCode()進行比較,如果相同,再調用equals()進行比較,都相同,就視為相同,不會添加到集合中。
- 實現類HashSet。底層通過Hash表實現,不可重復的特性是通過調用元素的hashCode()進行比較,如果相同,再調用equals()進行比較,都相同,就視為相同,不會添加到集合中。TreeSet,底層通過二叉樹實現,可對元素進行插入時就排序,它要求插入的元素比較實現Comparable接口,復寫compareTo()函數,或者在創建TreeSet時傳入自定義Comparator比較器對象,否則會在運行時拋出java.lang.ClassCastException類型的異常。LinkedHashSet,底層是使用LinkedHashMap,但它只使用了Map中的Key的唯一特性,來保證不可重復。
說說Map集合和它的實現類
- Map接口,專門用來實現鍵值對操作的集合類接口。它的實現類有HashMap、HashTable、TreeMap和LinkedHashMap。
- 實現類HashMap,底層使用Hash表實現,它通過元素的hashCode來確定存儲的位置,所以有很快的查詢速度,但它遍歷時的順序是不確定的。HashMap只允許一個key為null,但可以多個key的value為null。HashMap是非線程安全的,所以多線程環境下,對HashMap進行并發寫會出現不一致性問題,可以通過Collections的synchronizedMap()方法對HashMap進行包裝,讓HashMap具有線程安全的能力,或者使用ConcurrentHashMap。在JDK1.8之前,HashMap底層是使用Hash表和鏈表實現,當發生hash沖突時,同一個位置的元素會形成鏈表存儲,但是元素一多時,查詢就會變為鏈表的遍歷,效率比較低。在JDK1.8時,HashMap底層就變成Hash表和鏈表紅黑樹實現,當鏈表中的元素個數超過8時,鏈表轉換為紅黑樹,避免遍歷,優化了查詢效率。HashTable,底層和JDK1.7的HashMap類似,但它的key和value都不能為null,而且Hashtable是線程安全的,它的每個方法都有synchronized 關鍵字,所以多線程環境下可以使用它。TreeMap,底層是通過二叉樹實現,它能在元素添加時,進行排序,它要求元素必須實現Comparable接口,復寫compareTo()函數,或者在創建TreeMap時傳入自定義Comparator比較器對象,否則會在運行時拋出java.lang.ClassCastException類型的異常。LinkedHashMap,它是HashMap的一個子類,保存了插入順序,而其他Map實現類是無序的。
什么是泛型?什么是泛型擦除?
- 泛型可以對類型進行抽象,可以讓類型支持不同類型而重用,例如容器類ArrayList,通過泛型,ArrayList可以存儲不同的元素,并且泛型后的Array元素是具體類型而不是Object,避免了類型轉換的麻煩,而且編譯時會報錯,避免了類型轉換可能發生的類型轉換錯誤。
- 泛型擦除,因為Java的泛型是通過編譯時實現的,生成的Java字節碼中是不存在泛型信息的,所以泛型信息,在編譯器編譯時,會將泛型信息去除,這個過程就是泛型擦除。所以List上附加的泛型信息,在JVM來說是不可見的,在它眼里都是Object類型。
Java異常面試題
Java異常分為哪幾種?
- 編譯時異常
- 運行時異常
介紹一下Java的異常處理機制
- 捕獲異常,使用try-catch-finally
- 異常拋出,使用throws關鍵字
如果自定義一個異常
- 繼承一個異常類,運行時異常繼承RuntimeException,編譯時異常繼承Exception。
try-catch-finally,try中有return,finally還執行嗎?
- 執行,finally的執行在return之前,不管try中有沒有異常,都會執行。另外如果finally中有return,則會在try的return之前執行,那么try中的return就執行不到了。
Excption與Error的關系
- Excption與Error都是Throwable的子類。
- Excption有運行時異常和編譯時異常,他們都是可以捕獲和處理。編譯時異常常見有:IOException、FileNotFoundException、SQLException,必須在代碼中處理,否則編譯會報錯。運行時異常常見有:ClassCastException、IndexOutOfBoundsException、NullPointerException
- Error,和運行時異常一樣,編譯器也不會對錯誤進行檢查。當資源不足、約束失敗、或是其它程序無法繼續運行的條件發生時,就產生錯誤。程序本身無法修復這些錯誤的。常見子類有OutOfMemoryError
Java中的IO和NIO面試題
Java的IO流分為幾種
- 按流的流向分,可以分為輸入流和輸出流
- 按操作的單元分,可以分為字節流和字符流
- 按照流的角色分,可以分為節點流和處理流
Java IO流中40多個類,都是從以下4個抽象基類中派生出來的:
- InputStreamReader,所有輸入流的基類,InputStream為字符輸入流,Reader為字符輸入流。
- OutputStreamWriter,所有輸出流的基類,OutputStream為字節輸出流,Writer為字符輸出流。
Java中IO和NIO的區別?
- NIO被稱為New IO,在JDK1.4中引入,NIO和IO有相同的目的和作用,但實現方式不同。NIO主要用到的是塊,而IO是字節Byte,所以NIO的效率要比IO高很多。Java提供了2套NIO,一套針對文件,另一套針對網絡編程。
常用io類有哪些?
- 字節FileInputSteam、FileOutputStreamBufferInputStream、BufferedOutputSream
- 字符FileReader、FileWriterBufferReader、BufferedWriter
- 對象序列化ObjectInputStream、ObjectOutputSream
什么是Java NIO
- NIO 主要有三大核心部分: Channel(通道), Bu?er(緩沖區), Selector。
- 傳統 IO 基于字節流和字符流進行操作, 而 NIO 基于 Channel 和 Bu?er(緩沖區)進行操作,數據總是從通道讀取到緩沖區中,或者從緩沖區寫入到通道中。 Selector(選擇區)用于監聽多個通道的事件(比 如:連接打開,數據到達)。因此,單個線程可以監聽多個數據通道。
- NIO 和傳統 IO 之間第一個最大的區別是, IO 是面向流的, NIO 是 面向緩沖區的。
什么是NIO的Channel
- Channel,一般翻譯為通道。 Channel 和 IO 中的 Stream(流)是差不多一個等級的。 只不過 Stream 是單向的,譬如: InputStream, OutputStream, 而 Channel 是雙向的,既可以用來進行讀操作,又可以用來進行寫操作。
- NIO 中的 Channel 的主要實現類FileChannel(文件操作)DatagramChannel(UDP操作)SocketChannel(TCP客戶端)ServerSocketChannel(TCP服務端)
什么是NIO的Bu?er
- Bu?er,故名思意, 緩沖區,實際上是一個容器,是一個連續數組。 Channel 提供從文件、網絡讀取數據的渠道,但是讀取或寫入的數據 都必須經由 Bu?er。
- 從一個客戶端向服務端發送數據,然后服務端接收數據的過程。客戶端發送數據時,必須先將數據存入 Bu?er 中,然后將 Bu?er 中的內容寫入通道。服務端這邊接收數據必須通過 Channel 將數據讀入到 Bu?er 中,然后再從 Bu?er 中取出數據來處理。
- 在 NIO 中, Bu?er 是一個頂層父類,它是一個抽象類,常用的 Bu?er 的子類有ByteBu?erShortBu?erIntBu?erLongBu?erFloatBu?erDoubleBu?erCharBu?er
什么是NIO的Selector
- Selector 類是 NIO 的核心類, Selector 能夠檢測多個注冊的通道上是否有事件發生,如果有事件發生,便獲取事件然后針對每個事件進行 相應的響應處理。這樣一來,只是用一個單線程就可以管理多個通道,也就是管理多個連接。這樣使得只有在連接真正有讀寫事件發生時, 才會調用函數來進行讀寫,就大大地減少了系統開銷,并且不必為每個連接都創建一個線程,不用去維護多個線程,并且避免了多線程之間 的上下文切換導致的開銷。
Java反射面試題
Java反射創建對象效率高,還是new創建對象的效率高
- 通過new創建對象的效率比較高。通過反射時,先找查找類資源,使用類加載器創建,過程比較繁瑣,所以效率較低
Java反射的作用
- 反射機制是在運行時,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意個對象,都能夠調用它的任意一個方法。在java 中,只要給定類的名字,就可以通過反射機制來獲得類的所有信息。
哪里會用到反射機制
例如:加載MySQL的驅動類,如Hibernate、MyBatis等框架中會使用。
//加載MySQL的驅動類
Class.forName('com.mysql.jdbc.Driver.class');
復制代碼
反射機制的優缺點
- 優點能夠運行時動態獲取類的實例,提高靈活性與動態編譯結合
- 缺點使用反射性能較低,需要解析字節碼,將內存中的對象進行解析相對不安全,破壞了封裝性(因為通過反射可以獲得私有方法和屬性)
- 解決方案通過setAccessible(true),關閉JDK的安全檢查來提升反射速度第一次反射后,會有緩存,下次反射會快很多Re??ectASM工具類,通過字節碼生成的方式加快反射速度
Java注解面試題
注解是什么?
- Annotation(注解)是 Java 提供的一種對元程序中元素關聯信息和元數據(metadata)的途徑和方法。 Annatation(注解)是一個接口,程 序可以通過反射來獲取指定程序中元素的 Annotation對象,然后通過該 Annotation 對象來獲取注解中的元數據信息。
4種標準元注解是哪四種?
- @Target,修飾的對象范圍@Target說明了Annotation所修飾的對象范圍: Annotation可被用于 packages、types(類、接口、枚舉、Annotation 類型)、類型成員 (方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch 參數)。在 Annotation 類型的聲明中使用了 target 可更加明晰其修飾的目標
- @Retention,定義被保留的時間長短Retention 定義了該 Annotation 被保留的時間長短:表示需要在什么級別保存注解信息,用于描述注解的生命周期(即:被描述的注解在 什么范圍內有效),取值(RetentionPoicy)由:SOURCE:在源文件中有效(即源文件保留)CLASS:在 class 文件中有效(即 class 保留)RUNTIME:在運行時有效(即運行時保留)
- @Inherited 闡述了某個被標注的類型是被繼承的@Inherited 元注解是一個標記注解,@Inherited 闡述了某個被標注的類型是被繼承的。如果一 個使用了@Inherited 修飾的 annotation 類型被用于一個 class,則這個 annotation 將被用于該class 的子類。
Java多線程、并發面試題
Java中實現多線程有幾種方法
一共有4種方式
- 繼承Thread類
- 實現Runnable接口
- 實現Callable接口,通過FutureTask包裝器,來創建Thread線程
- 使用ExecutorService、Callable、Future實現有返回結果的多線程(也就是使用了ExecutorService,管理前面的三種方式)
如何停止一個正在運行的線程
- 使用退出標志,使線程正常退出,也就是當run()方法完成后線程終止
- 使用stop方法強行終止,但是不推薦這個方法,可能會導致線程操作的數據不一致
- 使用interrupt方法中斷線程,并捕獲InterruptedException異常
volatile是什么?可以保證有序性嗎?
- 共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之后,那么就具備了兩層語義保證不同線程對這個共享變量進行操作時,有可見性,就是其中一個線程對該變量值進行修改,其他線程是馬上可見的,volatile關鍵字會強制將修改的值同步到主內存。禁止指令重排,禁止編譯器優化代碼順序,避免在單例Double Check中導致多次初始化,保證有有序性。
- 注意,volatile不能保證原子性。
Thread 類中的start() 和 run() 方法有什么區別?
- start()方法被用來啟動新創建的線程,而且start()內部調用了run()方法,這和直接調用run()方法的效果不一樣。當你調用run()方法的時候,只會是在原來的線程中調用,沒有新的線程啟動,start()方法才會啟動新線程。
Java中synchronized 和 ReentrantLock 有什么不同?
- 相似點這兩種同步方式有很多相似之處,它們都是加鎖方式同步,而且都是阻塞式的同步,也就是說當如果一個線程獲得了對象鎖,進入了同步塊,其他訪問該同步塊的線程都必須阻塞在同步塊外面等待,而進行線程阻塞和喚醒的代價是比較高的。
- 區別對于Synchronized來說,它是java語言的關鍵字,是原生語法層面的互斥,需要jvm實現。而ReentrantLock它是JDK 1.5之后提供的API層面的互斥鎖,需要lock()和unlock()方法配 合try/?nally語句塊來完成。Synchronized進過編譯,會在同步塊的前后分別形成monitorenter和monitorexit這個兩個字節碼指令。在執行monitorenter指令時,首先要嘗試獲取對象鎖。如果這個對象沒被鎖定,或者當前線程已經擁有了那個對象鎖,把鎖的計數器加1,相應的,在執行monitorexit指令時會將鎖計數器就減1,當計數器為0時,鎖就被釋放了。如果獲取對象鎖失敗,那當前線程就要阻塞,直到對象鎖被另一個線程釋放為止 。
- ReentrantLock特性等待可中斷,持有鎖的線程長期不釋放的時候,正在等待的線程可以選擇放棄等待,這相當于Synchronized來說可以避免出現死鎖的情況。公平鎖,多個線程等待同一個鎖時,必須按照申請鎖的時間順序獲得鎖,Synchronized鎖非公平鎖, ReentrantLock默認的構造函數是創建的非公平鎖,可以通過參數true設為公平鎖,但公平鎖表現的性 能不是很好。鎖綁定多個條件,一個ReentrantLock對象可以同時綁定對個對象 。
SynchronizedMap和ConcurrentHashMap有什么區別?
- SynchronizedMap()和Hashtable一樣,實現上在調用map所有方法時,都對整個map進行同步。而 ConcurrentHashMap的實現卻更加精細,它對map中的所有桶加了鎖。所以,只要有一個線程訪問 map,其他線程就無法進入map,而如果一個線程在訪問ConcurrentHashMap某個桶時,其他線程, 仍然可以對map執行某些操作。
- 所以,ConcurrentHashMap在性能以及安全性方面,明顯比Collections.synchronizedMap()更加有優勢。同時,同步操作精確控制到桶,這樣,即使在遍歷map時,如果其他線程試圖對map進行數據修改,也不會拋出ConcurrentModi?cationException 。
Java線程池中submit() 和 execute()方法有什么區別?
兩個方法都可以向線程池提交任務。
- execute()方法,它的返回類型是void,任務執行完后,沒有返回值結果,它定義在Executor接口中。
- submit()方法,它可以返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口。
說一說自己對于 synchronized 關鍵字的了解
- synchronized關鍵字解決的是多個線程之間訪問資源的同步性,synchronized關鍵字可以保證被它修飾的方法或者代碼塊在任意時刻只能 有一個線程執行。
- 在 Java 早期版本中,synchronized屬于重量級鎖,效率低下,因為監視器鎖(monitor)是依賴于底層的操作系統的 Mutex Lock 來 實現的,Java 的線程是映射到操作系統的原生線程之上的。
- 如果要掛起或者喚醒一個線程,都需要操作系統幫忙完成,而操作系統實現線程之間的切換時需要從用戶態轉換到內核態,這個狀態之間的 轉換需要相對比較長的時間,時間成本相對較高,這也是為什么早期的synchronized 效率低的原因。
- 在 Java 6 之后 Java 官方對從 JVM 層面對synchronized 較大優化,所以現在的 synchronized 鎖效率也優化得很不錯了。JDK1.6對鎖的實現引入了大量的優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。
volatile關鍵字的作用?
- 一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之后,那么就具備了兩層語義: 保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。
- 禁止進行指令重排序。volatile本質是在告訴jvm當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取;synchronized則是鎖定當前變量, 只有當前線程可以訪問該變量,其他線程被阻塞住。volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的。volatile僅能實現變量的修改可見性,并不能保證原子性;synchronized則可以保證變量的修改可見性和原子性。volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。volatile標記的變量不會被編譯器優化;synchronized標記的變量可以被編譯器優化。
簡述一下你對線程池的理解
- 降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
- 提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
- 提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
線程生命周期
- 新建狀態(NEW)當程序使用 new 關鍵字創建了一個線程之后,該線程就處于新建狀態,此時僅由 JVM 為其分配內存,并初始化其成員變量的值。
- 就緒狀態(RUNNABLE)當線程對象調用了 start()方法之后,該線程處于就緒狀態。 Java 虛擬機會為其創建方法調用棧和程序計數器,等待調度運行。
- 運行狀態(RUNNING)如果處于就緒狀態的線程獲得了 CPU,開始執行 run()方法的線程執行體,則該線程處于運行狀態。
- 阻塞狀態(BLOCKED)阻塞狀態是指線程因為某種原因放棄了 cpu 使用權,也即讓出了 cpu timeslice,暫時停止運行。直到線程進入可運行(runnable)狀態,才 有機會再次獲得 cpu timeslice 轉到運行(running)狀態。阻塞的情況分三種等待阻塞(o.wait->等待對列),運行(running)的線程執行 o.wait()方法, JVM 會把該線程放入等待隊列(waitting queue)中。同步阻塞(lock->鎖池),運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則 JVM 會把該線程放入鎖池(lock pool)中。其他阻塞(sleep/join),運行(running)的線程執行 Thread.sleep(long ms)或 t.join()方法,或者發出了 I/O 請求時,JVM 會把該線程置為阻塞狀態。當 sleep()狀態 超時、 join()等待線程終止或者超時、或者 I/O處理完畢時,線程重新轉入可運行(runnable)狀態。
- 線程死亡(DEAD)線程會以下面三種方式結束,結束后就是死亡狀態。run()或 call()方法執行完成,線程正常結束。線程拋出一個未捕獲的 Exception 或 Error,線程異常結束。調用stop停止。直接調用該線程的 stop()方法來結束該線程—該方法通常容易導致死鎖,不推薦使用。
什么是樂觀鎖?
- 樂觀鎖是一種樂觀思想,即認為讀多寫少,遇到并發寫的可能性低,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新 的時候會判斷一下在此期間別人有沒有去更新這個數據,采取在寫時先讀出當前版本號,然后加鎖操作(比較跟上一次的版本號,如果一樣則更新),如果失敗則要重復,讀 比較 寫的操作。
- Java中的樂觀鎖基本都是通過 CAS 操作實現的, CAS 是一種更新的原子操作, 比較當前值跟傳入值是否一樣,一樣則更新,否則失敗。
什么是悲觀鎖?
- 悲觀鎖是就是悲觀思想,即認為寫多,遇到并發寫的可能性高,每次去拿數據的時候都認為別人會修改,所以每次在讀寫數據的時候都會上 鎖,這樣別人想讀寫這個數據就會 block 直到拿到鎖。Java中的悲觀鎖就是Synchronized,AQS框架下的鎖則是先嘗試CAS樂觀鎖去獲取鎖, 獲取不到,才會轉換為悲觀鎖,如RetreenLock。
什么是可重入鎖(遞歸鎖)
- 本文里面講的是廣義上的可重入鎖,而不是單指 JAVA 下的 ReentrantLock。 可重入鎖,也叫 做遞歸鎖,指的是同一線程 外層函數獲得鎖之后 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受 影響。在 JAVA 環境下 ReentrantLock 和 synchronized 都是 可重入鎖。
公平鎖與非公平鎖
- 公平鎖(Fair)加鎖前檢查是否有排隊等待的線程,優先排隊等待的線程,先到先得。
- 非公平鎖(Nonfair)加鎖時不考慮排隊等待問題,直接嘗試獲取鎖,獲取不到自動到隊尾等待
- 對比非公平鎖性能比公平鎖高 5~10 倍,因為公平鎖需要在多核的情況下維護一個隊列。Java 中的 synchronized 是非公平鎖, ReentrantLock 默認的 lock()方法采用的是非公平鎖。
在 Java 中 Executor 和 Executors 的區別?
- Executors 工具類的不同方法按照我們的需求創建了不同的線程池,來滿足業務的需求。
- Executor 接口對象能執行我們的線程任務。
- ExecutorService 接口繼承了 Executor 接口并進行了擴展,提供了更多的方法我們能獲得任務執行的狀態并且可以獲取任務的返回值。 使用 ThreadPoolExecutor 可以創建自定義線程池。
- Future 表示異步計算的結果,他提供了檢查計算是否完成的方法,以等待計算的 完成,并可以使用 get()方法獲取計算的結果。
MySQL面試題
什么是數據庫引擎?
- 數據庫存儲引擎是數據庫底層軟件組織,數據庫管理系統(DBMS)使用數據引擎進行創建、查詢、更新和刪除數據。不同的存儲引擎提供 不同的存儲機制、索引技巧、鎖定水平等功能,使用不同的存儲引擎,還可以 獲得特定的功能。現在許多不同的數據庫管理系統都支持多 種不同的數據引擎。
- 存儲引擎主要有: 1. MyIsam , 2. InnoDB, 3. Memory, 4. Archive, 5. Federated 。
InnoDB底層數據結構是什么?適用什么場景?
- InnoDB底層數據結構為B+樹,B+樹的每個節點對應InnoDB的一個page,page的大小是固定的,一般設為16k,其中非葉子節點只有鍵值,葉子節點包含數據。
- 適用場景經常更新的表,適合處理多重并發的更新請求。支持事務。可以從災難中恢復(通過bin-log日志等)外鍵約束(只有它支持外鍵約束)支持自動增加列屬性(auto_increment)
MyIASM的優點和缺點是什么?
- MyIASM是 MySQL默認的引擎
- 優點ISAM 執行讀取操作的速度很快,而且不占用大量的內存和存儲資源。
- 缺點不支持事務。表級鎖,不支持行級鎖和外鍵,因此當INSERT(插入)或UPDATE(更新)數據 時即寫操作需要鎖定整個表,效率會低一些。
InnoDB與MyISAM的區別
- InnoDB支持事務,MyISAM不支持,InnoDB將每條SQL語句都默認添加事務,自動提交,這樣會影響效率,所以最好將多條SQL語句放在begin和commit之間,組成一個事務。
- InnoDB支持外鍵,MyISAM不支持,如果將一個包含外鍵的InnoDB表轉為MyISAM會失敗。
- InnoDB是聚集索引,數據文件和索引是綁在一起的,必須有主鍵,通過主鍵查詢效率會很高。MyISAM是非聚集索引,數據文件和索引是分離的。
- InnoDB不保存表的具體行數,執行select count(*) from table時需要全表掃描,而MyISAM用一個變量保存,執行上面這個語句時,只要讀出該變量即可,速度很快。
- InnoDB不支持全文索引,而MyISAM支持,所以MyISAM的查詢效率比較高。
什么是索引?有幾種索引?索引越多越好嗎?
- 索引是加快檢索表中數據的方法,數據庫的索引類似書籍的索引,在書籍中,允許用戶不必翻閱整本書就能迅速的找到需要的信息,在數據庫中,索引也允許數據庫迅速地找到表中的數據,而不必掃描整個數據庫。
- MySQL中有4種不同的索引主鍵索引唯一索引普通索引全文索引
- 索引不是越多越好,創建索引也需要消耗資源,一是增加了數據庫的存儲空間,二是插入和刪除表數據,都要花較多的時間維護索引。
常見索引原則
- 字段是唯一的,建立唯一索引,可以更快速通過索引來確定某條記錄。
- 經常需要排序、分組、聯合操作的字段建立索引。
- 為常作為查詢條件的字段建立索引。
- 刪除不再使用或很少使用的索引。
- 索引列不能參加計算,帶函數的查詢,不參與索引。
- 最左前綴匹配原則。
數據庫的三范式是什么?
- 第一范式,列不可再分
- 第二范式,行有唯一區分的字段,主鍵約束
- 第三范式,表的非主屬性不能依賴與其他表的非主屬性外鍵約束
什么是數據庫事務?
- 事務(TRANSACTION)是作為單個邏輯工作單元執行的一系列操作, 這些操作作為一個整體一起向系統提交,要么都執行、要么都不執行。
- 事務是一個不可分割的工作邏輯單元,事務必須具備以下四個屬性,簡稱 ACID 屬性原子性(Atomicity)事務是一個完整的操作。事務的各步操作是不可分的(原子的);要么都執行,要么都不執 行。一致性(Consistency)當事務完成時,數據必須處于一致狀態。隔離性(Isolation)對數據進行修改的所有并發事務是彼此隔離的, 這表明事務必須是獨立的,它不應以任何方 式依賴于或影響其他事務。永久性(Durability)事務完成后,它對數據庫的修改被永久保持,事務日志能夠保持事務的永久性。
SQL優化
- 查詢語句中不要使用select *
- 盡量減少子查詢,使用關聯查詢(left join、right join、inner join)替代
- 減少使用IN或者NOT IN ,使用exists,not exists或者關聯查詢語句替代
- or 的查詢盡量用 union 或者 union all 代替(在確認沒有重復數據或者不用剔除重復數據時,union all會更好)
- 應盡量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。
- 應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如: select id from t where num is null 可以在num上設置默認值0,確保表中num列沒有null值,然后這樣查詢: select id from t where num = 0
drop、delete與truncate的區別
- delete和truncate只刪除表的數據不刪除表的結構。
- delete刪除記錄,不刪除表結構,delete語句可以加where,刪除操作會記錄在日志,可以回滾,刪除時,會激活觸發器。
- truncate刪除記錄,不刪除表結構,truncate語句不可以加where,刪除操作不記錄在日志,不能回滾,不會激活觸發器。
- drop會刪除記錄和結構,不會激活觸發器。
- 刪除速度來講,drop > truncate > delete
什么是內聯接、左外聯接、右外聯接?
- 內聯接(Inner Join):匹配2張表中相關聯的記錄。(2張表中沒有關聯的部分拋棄)
- 左外聯接(Left Outer Join):除了匹配2張表中相關聯的記錄外,還會匹配左表中剩余的記錄,右表中未匹配到的字段用NULL表示。(以左邊記錄匹配,如果右表中沒有記錄與其匹配,字段值為NULL)
- 右外聯接(Right Outer Join):除了匹配2張表中相關聯的記錄外,還會匹配右表中剩余的記錄,左表中未匹配到的字段用NULL表示。(以右邊記錄匹配,如果左表中沒有記錄與其匹配,字段值為NULL)
并發事務帶來哪些問題?
- 臟讀(Dirty read),當一個事務讀取了數據,并且修改了,但還未提交到數據庫中,另外一個事務也讀取了數據,并且使用了該數據,這時另外一個數據讀取到的數據就是“臟數據”,根據“臟數據”所做的處理可能是不正確的。
- 丟失修改(Lost to modify),當一個事務讀取了數據,另外一個事務也讀取了數據,在第一個事務修改了數據后,第二個事務也修改了數據,這樣第一個事務的修改則被丟失,因為為“丟失修”,例如事務1讀取了數據A=20,事務2也讀取了A=20,事務1修改A=A1,事務2也修改A=A-1,最終結果為A=19,事務1的修改被丟失了。
- 不可重復讀(Unrepeatableread),指一個事務多次讀取1個數據,在這個事務還未結束時,另外一個事務也訪問該數據,如果第二個事務修改了數據,導致第一個事務的多次讀取的數據結果可能是不一樣的,因此成為不可重復讀。
- 幻讀(Phantom read),幻讀和不可重復讀類似,它發生在一個事務讀取了幾行數據,接著另外一個事務插入了一些數據,在隨后的查詢中,第一個事務發現多了一些原本不存在的數據,就像產生了幻覺一樣,所以稱為幻讀。
不可重復讀和幻讀的區別
- 不可重復讀的重點是修改,例如多次讀取一條記錄,發現記錄的某一列的值被修改。
- 幻讀的重點是新增或減少,例如多次讀取,發現記錄增多或減少了。
事務隔離級別有哪些?MySQL的默認隔離級別是?
SQL 標準定義了四個隔離級別
- READ-UNCOMMITTED(讀取未提交):最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致臟讀、幻讀或不可重復讀。
- READ-COMMITTED(讀取已提交): 允許讀取并發事務已經提交的數據,可以阻止臟讀,但是幻讀或不可重復讀仍有可能發生。
- REPEATABLE-READ(可重復讀): 對同一字段的多次讀取結果都是一致的,除非數據是被事務本身自己所修改,可以阻止臟讀和不可重復 讀,但幻讀仍有可能發生。
- SERIALIZABLE(可串行化): 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。
- MySQL InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀),我們可以通過 SELECT @@tx_isolation; 命令來查看。
但要注意,MySQL InnoDB在 REPEATABLE-READ(可重讀)隔離級別下,使用的是Next-Key Lock 鎖算法,因此可以避免幻讀的產生,所以MySQL默認的的隔離級別,REPEATABLE-READ級別也達到了SERIALIZABLE(可串行化)級別的隔離要求。因為級別越高,事務請求的鎖越多,所以大部分的數據庫隔離級別都是READ-COMMITTED(讀取已提交)。
大表如何優化?
- 限定查詢數據的范圍,例如查詢訂單歷史時,控制查詢一個月內的訂單。
- 讀、寫分離,主庫復寫寫,從庫負責讀。
- 垂直分區,例如用戶表既有用戶的登錄信息,也有用戶的基本信息,可以進行垂直拆分,把用戶表拆分為2張表,就是把數據列拆分到多張表。
- 水平分區,保持表結構不變,通過某種策略將數據分散到不同的表或庫中,例如根據年、月,一年一張表,一月一張表,水平分區可以支持非常大的數據量。水平分區的分表只能解決單張表的存儲數據量過大問題,但由于數據還是在一臺機器上,對于提供并發能力并沒有什么意義,所以水平拆分最好分庫。
分庫分表后,主鍵id如何處理
分庫分表后,每個表的id都是從1開始累加,這樣是不對的,我們需要一個全局唯一id來支持。
- UUID,太長了,并且無序,查詢效率低,比較用于生成文件名等。
- 數據自增Id,每臺數據庫分別設置不同的步長,生成不重復的ID,這種方式生成ID有序,但是需要獨立部署數據庫實例,成本高,還有性能瓶頸。
- 利用redis生成id,性能好,靈活方便,不依賴于數據庫,但引入了新的組件造成系統更復雜,可用性降低,編碼更加復雜,增加了系統成本。
- Twitter的snow?ake雪花算法。
- 美團的Leaf分布式ID生成系統,Leaf 是美團開源的分布式ID生成器,能保證全局唯一性、趨勢遞增、單調遞增、信息安全,但也依賴關系數據庫,Zookeeper等中間件。
MySQL中有哪幾種鎖?
- 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖沖突的概率最高,并發度最低。
- 頁面鎖:開銷和加鎖時間界于表鎖和行鎖之間;會出現死鎖;鎖定粒度界于表鎖和行鎖之間,并發度一般
- 行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖沖突的概率最低,并發度也最高。
NOW() 和 CURRENT_DATE() 有什么區別?
- NOW() 命令用于顯示當前年份,月份,日期,小時,分鐘和秒。
- CURRENT_DATE() 僅顯示當前年份,月份和日期。
鎖的優化策略
- 讀寫分離
- 分段加鎖
- 減少鎖持有的時間
- 多個線程盡量以相同的順序去獲取資源不能將鎖的粒度過于細化,不然可能會出現線程的加鎖和釋放次數過多,反而效率不如一次加一把大鎖。
索引的底層實現原理和優化
- 底層是B+樹,經過優化的 B+樹。
- 主要是在所有的葉子結點中增加了指向下一個葉子節點的指針,因此 InnoDB 建議為大部分表使用默認自增的主鍵作為主索引。
索引的目的是什么?
- 快速訪問數據表中的特定信息,提高檢索速度。
- 加速表和表之間的連接,使用分組和排序子句進行數據檢索時,可以顯著減少查詢中分組和排序的時間。
- 創建唯一性索引,保證數據庫表中每一行數據的唯一性。
索引對數據庫系統的負面影響是什么?
- 創建索引和維護索引需要耗費時間,這個時間隨著數據量的增加而增加;索引需要占用物理空間,不光是表需要占用數據空間,每個索引也需要占用物理空間;當對表進行增、刪、改、的時候索引也要動態維護,這樣就降低了數據的維護速度。
為數據表建立索引的原則有哪些?
- 在最頻繁使用的、用以縮小查詢范圍的字段上建立索引。
- 在頻繁使用的、需要排序的字段上建立索引。
什么情況下不宜建立索引?
- 對于查詢中很少涉及的列或者重復值比較多的列,不宜建立索引。
- 對于一些特殊的數據類型,不宜建立索引,比如文本字段(text)等。
什么情況索引會失效
- 以“%”開頭的 LIKE 語句,模糊匹配
- OR 語句前后沒有同時使用索引
- 數據類型出現隱式轉化(如 varchar 不加單引號的話可能會自動轉換為 int 型)
實踐中如何優化 MySQL
- SQL 語句及索引的優化
- 數據庫表結構的優化
- 系統配置的優化
- 硬件的優化
優化數據庫的方法
- 選取最適用的字段屬性,盡可能減少定義字段寬度,盡量把字段設置 NOT NULL,例如’省份’、’性別’最好使用 ENUM 枚舉
- 使用連接(JOIN)來代替子查詢
- 適用聯合(UNION)來代替手動創建的臨時表
- 事務處理
- 鎖定表、優化事務處理
- 適用外鍵,優化鎖定表
- 建立索引
- 優化查詢語句
簡單描述 MySQL 中,索引,主鍵,唯一索引,聯合索引的區別,對數據庫的性能有什么影響(從讀寫兩方面)
- 索引是一種特殊的文件(InnoDB 數據表上的索引是表空間的一個組成部分),它們包含著對數據表里所有記錄的引用指針。
- 普通索引(由關鍵字 KEY 或 INDEX 定義的索引)的唯一任務是加快對數據的訪問速度。
- 普通索引允許被索引的數據列包含重復的值。如果能確定某個數據列將只包含彼此各不相同的值,在為這個數據列創建索引的時候就應該用 關鍵字 UNIQUE 把它定義為一個唯一索引。也就是說,唯一索引可以保證數據記錄的唯一性。
- 主鍵,是一種特殊的唯一索引,在一張表中只能定義一個主鍵索引,主鍵用于唯一標識一條記錄,使用關鍵字 PRIMARY KEY 來創建。
- 索引可以覆蓋多個數據列,如像 INDEX(columnA, columnB)索引,這就是聯合索引。
- 索引可以極大的提高數據的查詢速度,但是會降低插入、刪除、更新表的速度,因為在執行這些寫操作時,還要操作索引文件。
SQL 注入漏洞產生的原因?如何防止?
- SQL 注入產生的原因:程序開發過程中不注意規范書寫 sql 語句和對特殊字符進行過濾,導致客戶端可以通過全局變量 POST 和 GET 提交 一些 sql 語句正常執行。
- 防止 SQL 注入的方式開啟配置文件中的 magic_quotes_gpc 和 magic_quotes_runtime 設置 執行 sql 語句時使用 addslashes 進行 sql 語句轉換Sql 語句書寫盡量不要省略雙引號和單引號。過濾掉 sql 語句中的一些關鍵詞:update、insert、delete、select、 * 。提高數據庫表和字段的命名技巧,對一些重要的字段根據程序的特點命名,取不易被猜到的。
最后
今天的面試題分享就到這里了,說是200道,其實阿博也沒怎么數過,有耐心的朋友可以數一下,評論區給阿博看看!