前言
近間陸續面試了不少的求職的前(JAVA)、后(WEB)端開發人員,包括實習生、應屆畢業生、一兩年工作經驗的、也有三四年工作經驗的,也算見過了比較多的開發人員,想在這里做個總結,本次主要講一講面試和后端(java)相關的東西;
關於面試准備
JAVA基礎(答案僅供參考,如有不對之處請批評指正)
a、HashMap是非線程安全的,HashTable是線程安全的。
b、HashMap的鍵和值都允許有null值存在,而HashTable則不行。
c、因為線程安全的問題,HashMap效率比HashTable的要高。
數據結構和算法
1、B+樹
參考:B+樹介紹
2、八大排序算法
參考:八大排序算法JAVA實現
JVM
1、JVM的內存結構
答:主要分為三大塊 堆內存、方法區、棧;棧又分為JVM棧、本地方法棧
堆(heap space),堆內存是JVM中最大的一塊,有年輕代和老年代組成,而年輕代又分為三分部分,Eden區,From Survivor,To Survivor,默認情況下按照8:1:1來分配
方法區(Method area),存儲類信息、常量、靜態變量等數據,是線程共享的區域
程序計數器(Program counter Register),是一塊較小的內存空間,是當前線程所執行的字節碼的行號指示器
JVM棧(JVM stacks),也是線程私有的,生命周期與線程相同,每個方法被執行時都會創建一個棧幀,用於存儲局部變量表、操作棧、動態鏈接、方法出口等信息
本地方法棧(Native Mthod Stacks),為虛擬機使用的native方法服務
2、關於垃圾回收和常見的GC算法,請參考:GC專家系列-理解java垃圾回收
多線程
1、JAVA實現多線程的幾種方式
a、繼承Thread類實現
public class MyThread extends Thread { public void run() { System.out.println("MyThread.run()"); } } MyThread myThread1 = new MyThread(); MyThread myThread2 = new MyThread(); myThread1.start(); myThread2.start();
b、實現Runnable接口
public class MyThread extends OtherClass implements Runnable { public void run() { System.out.println("MyThread.run()"); } } MyThread myThread = new MyThread(); Thread thread = new Thread(myThread); thread.start();
c、使用ExecutorService、Callable、Future實現有返回結果的多線程
import java.util.concurrent.*; import java.util.Date; import java.util.List; import java.util.ArrayList; /** * 有返回值的線程 */ @SuppressWarnings("unchecked") public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("----程序開始運行----"); Date date1 = new Date(); int taskSize = 5; // 創建一個線程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 創建多個有返回值的任務 List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new MyCallable(i + " "); // 執行任務並獲取Future對象 Future f = pool.submit(c); // System.out.println(">>>" + f.get().toString()); list.add(f); } // 關閉線程池 pool.shutdown(); // 獲取所有並發任務的運行結果 for (Future f : list) { // 從Future對象上獲取任務的返回值,並輸出到控制台 System.out.println(">>>" + f.get().toString()); } Date date2 = new Date(); System.out.println("----程序結束運行----,程序運行時間【" + (date2.getTime() - date1.getTime()) + "毫秒】"); } } class MyCallable implements Callable<Object> { private String taskNum; MyCallable(String taskNum) { this.taskNum = taskNum; } public Object call() throws Exception { System.out.println(">>>" + taskNum + "任務啟動"); Date dateTmp1 = new Date(); Thread.sleep(1000); Date dateTmp2 = new Date(); long time = dateTmp2.getTime() - dateTmp1.getTime(); System.out.println(">>>" + taskNum + "任務終止"); return taskNum + "任務返回運行結果,當前任務時間【" + time + "毫秒】"; } }
2、Callable和Future
答:Callable接口類似於Runnable,但是Runnable不會返回結果,並且無法拋出返回結果的異常,而Callable更強大,被線程執行以后,可以返回值,這個返回值就是通過Future拿到,也就是說,Future可以拿到異步執行任務的返回值,可以看以下例子:
import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Test { public static void main(String[] args) { Callable<Integer> callable = new Callable<Integer>() { @Override public Integer call() throws Exception { return new Random().nextInt(100); } }; FutureTask<Integer> futureTask = new FutureTask<Integer>(callable); new Thread(futureTask).start(); try { Thread.sleep(1000); System.err.println(futureTask.get()); } catch (Exception e) { e.printStackTrace(); } } }
ExecutorService繼承自Executor,目的是為我們管理Thread對象,從而簡化並發變成,Executor使我們無需顯示的去管理線程的聲明周期,是JDK5之后啟動任務的首選方式。
執行多個帶返回值的任務,並取得多個返回值,代碼如下:
import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class CallableAndFuture { public static void main(String[] args) { ExecutorService threadPool = Executors.newCachedThreadPool(); CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(threadPool); for( int i = 0; i < 5; i++ ){ final int taskId = i; cs.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { return taskId; } }); } for( int i = 0; i < 5; i++ ){ try { System.err.println(cs.take().get()); } catch (Exception e) { e.printStackTrace(); } } } }
3、線程池的參數有哪些,在線程池創建一個線程的過程
corePoolSize:核心線程數,能夠同時執行的任務數量
maximumPoolSize:除去緩沖隊列中等待的任務,最大能容納的任務數(其實就是包括了核心線程池的數量)
keepAliveTime:超出workQueue的等待任務的存活時間,就是指maximumPoolSize里面的等待任務的存活等待時間
unit:時間單位
workQueue:阻塞等待線程的隊列,一般使用new LinkedBlockingQueue()這個,如果不指定容量,會一直往里添加,沒有限制,workQueue永遠不會滿,一般選擇沒有容量上限的隊列
threadFactory:創建線程的工廠,使用系統默認的類
handler:當任務數超過maximumPoolSize時,對任務的處理策略,默認策略是拒絕添加
執行流程:當線程數小於corePoolSize時,每添加一個任務,則立即開啟線程執行;當corePoolSize滿的時候,后面添加的任務將放入緩沖隊列workQueue等待;當workQueue滿的時候,看是否超過maximumPoolSize線程數,如果超過,則拒絕執行,如果沒有超過,則創建線程理解執行;
1 import java.util.concurrent.Executors; 2 import java.util.concurrent.LinkedBlockingQueue; 3 import java.util.concurrent.ThreadPoolExecutor; 4 import java.util.concurrent.TimeUnit; 5 6 /** 7 * 對線程池進行管理和封裝 8 * @author guoqing 9 * 10 */ 11 public class ThreadPoolManager { 12 13 private static ThreadPoolManager mInstance = new ThreadPoolManager(); 14 private ThreadPoolExecutor executor; 15 16 private int corePoolSize; //核心線程池數量,表示能夠同時執行的任務數量 17 private int maximumPoolSize; //最大線程池數量,其實是包含了核心線程池數量在內的 18 private long keepAliveTime = 1; //存活時間,表示最大線程池中等待任務的存活時間 19 private TimeUnit unit = TimeUnit.HOURS; //存活時間的時間單位 20 21 public static ThreadPoolManager getInstance() { 22 return mInstance; 23 } 24 25 private ThreadPoolManager() { 26 //核心線程數量的計算規則:當前設備的可用處理器核心數*2+1,能夠讓cpu得到最大效率的發揮 27 corePoolSize = Runtime.getRuntime().availableProcessors()*2+1; 28 maximumPoolSize = corePoolSize; //雖然用不到,但是不能為0,否則會報錯 29 //線程池機制:領工資的機制 30 executor = new ThreadPoolExecutor(corePoolSize, 31 maximumPoolSize, 32 keepAliveTime, 33 unit, 34 new LinkedBlockingQueue<Runnable>(), //緩沖隊列,超出核心線程池的任務會被放入緩沖隊列中等待 35 Executors.defaultThreadFactory(), //創建線程的工廠類 36 new ThreadPoolExecutor.AbortPolicy() //當最大線程池也超出的時候,則拒絕執行 37 ); 38 } 39 40 /** 41 * 往線程池中添加任務 42 * @param r 43 */ 44 public void executor(Runnable r) { 45 if(r!=null) { 46 executor.execute(r); 47 } 48 } 49 50 /** 51 * 從線程池中移除任務 52 * @param r 53 */ 54 public void remove(Runnable r) { 55 if(r!=null) { 56 executor.remove(r); 57 } 58 } 59 }
4、volatile關鍵字的作用,原理
答:保證內存可見性和禁止指令重排。實現原理可參考:JAVA並發變成--valatile關鍵字剖析
5、synchronized關鍵字的用法,優缺點
答:java關鍵字,當它用來修飾一個方法或者代碼塊的時候,能夠保證在同一時刻最多只有一個線程執行該代碼段的代碼;
synchronized修飾的方法或者對象,只能以同步的方式執行,會引起性能問題;無法中斷一個正在等候獲得鎖的線程,也無法通過投票獲得鎖;一個優先級高的線程等待一個優先級低的線程釋放鎖會導致優先級倒置,引起性能風險;
6、Lock接口有哪些實現類,使用場景是什么
答:Lock接口有三個實現類,一個是ReentrantLock,另兩個是ReentrantReadWriteLock類中的兩個靜態內部類ReadLock和WriteLock。
使用場景:一般應用於多度少寫,因為讀的線程之間沒有競爭,所以比起synchronzied,性能要好很多;
10、sleep和wait的區別
答:首先,sleep()方法屬於Thread類的,而wait()方法是屬於Object類的;sleep()方法導致了程序暫停執行指定的時間,讓出cpu給其他線程,但是他的監控狀態依然保持,當指定的時間到了又自動回恢復運行狀態,調用了sleep()方法的過程中,線程不會釋放對象鎖;而當調用了wait()方法的時候,線程回放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法后本線程才進入對象鎖定池准備。
數據庫相關
1、常見的數據庫優化手段
答:庫表優化,表設計合理化,符合三大范式;添加適當的索引(普通索引、主鍵索引、唯一索引、全文索引);分庫分表;讀寫分離等;sql語句優化,定位執行效率低,慢sql的語句,通過explain分析低效率的原因;
2、索引的優缺點,什么字段上建立索引
答:優點方面:第一,通過創建唯一索引可以保證數據的唯一性;第二,可以大大加快數據的檢索速度,是主要目的;第三;在使用分組和排序子句進行數據檢索時,可以顯著減少查詢中分組和排序的時間;第四,可以在查詢中使用優化隱藏器,提高系統的性能;
缺點方面:第一,創建索引和維護索引要耗費時間,並且隨着數據量的增加而增加;第二,每一個索引需要占用額外的物理空間,需要的磁盤開銷更大;第三,當對表中的數據進行增加、刪除、修改操作時,索引也要動態維護,降低了數據的維護速度;
計算機網絡
1、TCP和UDP的區別
答:TCP(傳輸控制協議),UDP(用戶數據報協議)
(1)TCP面向連接(如打電話先撥號建立連接);UDP是無連接的,即發送數據之前不需要建立連接;
(2)TCP提供可靠的服務。也就是說,通過TCP連接傳送的數據,無差錯,不丟失,不重復,且按序達到;UDP盡最大努力交付,即不保證可靠交付;
(3)TCP面向字節流,實際上是TCP把數據看成一連串無結構的字節流;UDP是面向報文,UDP沒有擁塞控制,因此網絡出現擁塞不會使源主機的發送速率降低(對實時應用很有用,如IP電話,實時視頻會議等)
(4)每一條TCP連接只能是點到點的,UDP支持一對一,一對多,多對一和多對多的交互通信;
(5)TCP首部開銷20字節,UDP首部開銷8字節;
(6)TCP的邏輯通信信道是全雙工的可靠信道,DUP則是不可靠信道;
四次揮手:
A:“喂,我不說了 (FIN)。”A->FIN_WAIT1
B:“我知道了(ACK)。等下,上一句還沒說完。Balabala…..(傳輸數據)”B->CLOSE_WAIT | A->FIN_WAIT2
B:”好了,說完了,我也不說了(FIN)。”B->LAST_ACK
A:”我知道了(ACK)。”A->TIME_WAIT | B->CLOSED
A等待2MSL,保證B收到了消息,否則重說一次”我知道了”,A->CLOSED

3、長連接和短連接。
短連接:連接=》傳輸數據=》關閉連接
HTTP是無狀態的,瀏覽器和服務器之間每進行一次http操作,就建立一次連接,但任務結束就中斷連接;也可以理解為短連接是指socket連接后,發送接收完數據馬上斷開連接;
長連接:連接=》傳輸數據=》保持連接=》傳輸數據=》。。。=》關閉連接
長連接指建立socket連接后不管是否使用都保持連接,但安全性較差;
設計模式
此處推薦閱讀:java23種設計模式 深入理解
1、單例模式的幾種寫法
懶漢模式
public class Singleton { private static Singleton instance = null; private Singleton(){} public static synchronized Singleton getInstance(){ //如果還沒有被實例化過,就實例化一個,然后返回 if(instance == null){ instance = new Singleton(); } return instance; } }
餓漢模式
public class Singleton { //類加載的時候instance就已經指向了一個實例 private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; } }
雙重檢驗鎖
public class Singleton { private static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ synchronized (Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } }
靜態內部類:因為JAVA靜態內部類的特性,加載的時候不會加載內部靜態類,使用的時候才會加載,而使用的時候類加載又是線程安全的,這就完美達到了效果;
public class Singleton { private static class SingletonHolder{ private static Singleton instance = new Singleton(); } private Singleton(){} public static Singleton getInstance(){ return SingletonHolder.instance; } }
枚舉:
public enum Singleton { INSTANCE; }
2、Spring使用了哪些設計模式
(1)工廠模式,在各種BeanFactory以及ApplicationContext創建中都用到了;
(2)模板模式,也是在各種BeanFactory以及ApplicationContext創建中都用到了;
(3)代理模式,在AOP實現中用到了JDK的動態代理;
(4)單例模式,比如創建bean的時候;
(5)策略模式,第一個地方,加載資源文件的地方,使用了不同的方法,比如:classPathResource,FileSystemResource,ServletContextResource,UrlResource但他們都有共同的接口Resource;第二個地方就是AOP的實現中,采用了不同的方式,JDK動態代理和CGLIB代理;
分布式相關
1、分布式事務的控制
可以參考分布式系統事務一致性解決方案
2、分布式鎖
答:一般使用zk瞬時有序節點實現的分布式鎖,或者利用redis的setnx()封裝分布式鎖;提供思路,具體的可以自行詳細理解;
3、分布式session如何設計
答:一個比較成熟的方案是通過redis進行session共享。詳細的原理可以參考一種分布式session實現方案
4、關於dubbo
可以參考博文:Dubbo學習總結(2)——Dubbo架構詳解
5、可以了解zk相關知識
緩存相關
1、redis和memcached的區別
(1)redis和memcache都是將數據放入內存中,都是內存數據庫。但是memcache可以緩存圖片、視頻等數據;
(2)redis不僅僅支持簡單的k/v數據,還提供list、set、hash等數據結構的存儲;
(3)虛擬內存--redis當物理內存用完時,可以將一些很久沒有用到的value交換到磁盤;
(4)過期策略--memcache在set時就指定,例如set key1008,即永不過期,redis通過expire設定;
(5)分布式--設定memcache集群,利用magent做一主多從;redis可以做一主多從或一主一從;
(6)存儲數據安全--memcache掛掉后,數據沒了,redis可以定期保存到磁盤進行持久化;
(7)災難恢復--memcache掛掉后,數據不可恢復。redis數據丟失后可以通過aof恢復;
(8)redis支持數據備份,即master-slave主備模式;
2、redis是單線程的么(是的)
3、redis的持久化策略
答:rdb:快照形式是直接把內存中的數據保存到一個dump文件中,定時保存
aof:把所有的對redis的服務器進行修改的命令都存到一個文件里,命令的集合
框架相關
1、SpringMvc工作原理
(1)用戶發送請求至前端控制器DispatcherServlet
(2)DispatcherServlet收到請求調用HandlerMapping處理映射器
(3)處理器映射器找到具體的處理器(可以根據xml配置、注解進行查找),生成處理器對象及處理器攔截器(如有則生成)一並返回給DispatcherServlet
(4)DispatcherServlet調用HandlerAdapter處理器映射器
(5)HandlerAdapter經過適配調用具體的處理器(Controller,也叫后端控制器)
(6)Controller執行完成返回ModelAndView
(7)HandlerAdapter將Controller執行結果ModelAndView返回給DispatcherServlet
(8)DispatcherServlet將ModelAndView傳給ViewResolver視圖解析器
(9)ViewResolver解析后返回具體的view
(10)DispatcherServlet根據view進行試圖渲染(即將模型數據填充至視圖中)
(11)DispatcherServlet響應用戶
以下組件通常使用框架提供實現:
DispatcherServlet:作為前端控制器,整個流程控制的中心,控制其它組件執行,統一調度,降低組件之間的耦合性,提高每個組件的擴展性。
HandlerMapping:通過擴展處理器映射器實現不同的映射方式,例如:配置文件方式,實現接口方式,注解方式等。
HandlAdapter:通過擴展處理器適配器,支持更多類型的處理器。
ViewResolver:通過擴展視圖解析器,支持更多類型的視圖解析,例如:jsp、freemarker、pdf、excel等。
2、Quartz概念及原理
org.quartz.Job:它是一個抽象接口,表示一個工作,也是我們要執行的具體的內容,只定義了一個接口方法:void execute(JobExecutionContext context)
org.quartz.JobDetail:JobDetail表示一個具體的可執行的調度程序,Job是這個可執行調度程序所要執行的內容,它包含了這個調度任務的方案和策略
org.quartz.Trigger:Trigger是一個抽象接口,表示一個調度參數的配置,通過配置他,來告訴調度器什么時候去調用JobDetail
org.quartz.Scheduler:一個調度容器,可以注冊多個Trigger和JobDetail。當Trigger和JobDetail組合,就可以被Scheduler容器調度了
3、Spring的IOC有什么優勢
答:要了解IOC首先要明白依賴倒置原則(Dependency Inversion Principle),就是把原本的高層建築依賴底層建築倒置過來,變成底層建築依賴高層建築。高層建築決定需要什么,底層去實現這樣的需求,但是高層並不用管底層的是怎么實現的;而控制反轉(Inversion of Control)就是依賴倒置原則的一種代碼的設計思路;

IOC思想的核心,資源不由使用資源的雙方管理,由不適用資源的第三方管理。
優勢:資源集中管理,實現資源的可配置和易管理;降低了使用資源雙方的依賴程度,也就是降低了耦合度;
4、Mybatis的設計思想,以及動態代理的真正實現
Mybatis中的mapper沒有實現類,只有對應的xml文件,是如何實現的;
Spring整合Mybatis時sqlsession為何不需要自動釋放或關閉;
