查看: 3226|回復: 0

[Java學習] 2017各大公司Java后端開發面試題總結

3萬

主題

3萬

帖子

10萬

積分

管理員

Rank: 9Rank: 9Rank: 9

積分
100197
發表于 2017-3-8 20:22:33
ThreadLocal(線程變量副本)

Synchronized實現內存共享,ThreadLocal為每個線程維護一個本地變量。
采用空間換時間,它用于線程間的數據隔離,為每一個使用該變量的線程提供一個副本,每個線程都可以獨立地改變自己的副本,而不會和其他線程的副本沖突。
ThreadLocal類中維護一個Map,用于存儲每一個線程的變量副本,Map中元素的鍵為線程對象,而值為對應線程的變量副本。
ThreadLocal在spring中發揮著巨大的作用,在管理Request作用域中的Bean、事務管理、任務調度、AOP等模塊都出現了它的身影。
Spring中絕大部分Bean都可以聲明成Singleton作用域,采用ThreadLocal進行封裝,因此有狀態的Bean就能夠以singleton的方式在多線程中正常工作了。



Java內存模型:

Java虛擬機規范中將Java運行時數據分為六種。
1.程序計數器:是一個數據結構,用于保存當前正常執行的程序的內存地址。Java虛擬機的多線程就是通過線程輪流切換并分配處理器時間來實現的,為了線程切換后能恢復到正確的位置,每條線程都需要一個獨立的程序計數器,互不影響,該區域為“線程私有”。
2.Java虛擬機棧:線程私有的,與線程生命周期相同,用于存儲局部變量表,操作棧,方法返回值。局部變量表放著基本數據類型,還有對象的引用。
3.本地方法棧:跟虛擬機棧很像,不過它是為虛擬機使用到的Native方法服務。
4.Java堆:所有線程共享的一塊內存區域,對象實例幾乎都在這分配內存。
5.方法區:各個線程共享的區域,儲存虛擬機加載的類信息,常量,靜態變量,編譯后的代碼。
6.運行時常量池:代表運行時每個class文件中的常量表。包括幾種常量:編譯時的數字常量、方法或者域的引用。



“你能不能談談,JavaGC是在什么時候,對什么東西,做了什么事情?”
在什么時候:
1.新生代有一個Eden區和兩個survivor區,首先將對象放入Eden區,如果空間不足就向其中的一個survivor區上放,如果仍然放不下就會引發一次發生在新生代的minor GC,將存活的對象放入另一個survivor區中,然后清空Eden和之前的那個survivor區的內存。在某次GC過程中,如果發現仍然又放不下的對象,就將這些對象放入老年代內存里去。
2.大對象以及長期存活的對象直接進入老年區。
3.當每次執行minor GC的時候應該對要晉升到老年代的對象進行分析,如果這些馬上要到老年區的老年對象的大小超過了老年區的剩余大小,那么執行一次Full GC以盡可能地獲得老年區的空間。
對什么東西:從GC Roots搜索不到,而且經過一次標記清理之后仍沒有復活的對象。
做什么:
新生代:復制清理;
老年代:標記-清除和標記-壓縮算法
永久代:存放Java中的類和加載類的類加載器本身。
GC Roots都有哪些:
1. 虛擬機棧中的引用的對象
2. 方法區中靜態屬性引用的對象,常量引用的對象
3. 本地方法棧中JNI(即一般說的Native方法)引用的對象。



Synchronized 與Lock都是可重入鎖,同一個線程再次進入同步代碼的時候.可以使用自己已經獲取到的鎖。
Synchronized是悲觀鎖機制,獨占鎖。而Locks.ReentrantLock是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。
ReentrantLock適用場景

  • 某個線程在等待一個鎖的控制權的這段時間需要中斷
  • 需要分開處理一些wait-notify,ReentrantLock里面的Condition應用,能夠控制notify哪個線程,鎖可以綁定多個條件。
  • 具有公平鎖功能,每個到來的線程都將排隊等候。




StringBuffer是線程安全的,每次操作字符串,String會生成一個新的對象,而StringBuffer不會;StringBuilder是非線程安全的




fail-fast:機制是java集合(Collection)中的一種錯誤機制。當多個線程對同一個集合的內容進行操作時,就可能會產生fail-fast事件。
例如:當某一個線程A通過iterator去遍歷某集合的過程中,若該集合的內容被其他線程所改變了;那么線程A訪問集合時,就會拋出ConcurrentModificationException異常,產生fail-fast事件



happens-before:如果兩個操作之間具有happens-before 關系,那么前一個操作的結果就會對后面一個操作可見。
1.程序順序規則:一個線程中的每個操作,happens- before 于該線程中的任意后續操作。
2.監視器鎖規則:對一個監視器鎖的解鎖,happens- before 于隨后對這個監視器鎖的加鎖。
3.volatile變量規則:對一個volatile域的寫,happens- before于任意后續對這個volatile域的讀。
4.傳遞性:如果A happens- before B,且B happens- before C,那么A happens- before C。
5.線程啟動規則:Thread對象的start()方法happens- before于此線程的每一個動作。


Volatile和Synchronized四個不同點:
1 粒度不同,前者鎖對象和類,后者針對變量
2 syn阻塞,volatile線程不阻塞
3 syn保證三大特性,volatile不保證原子性
4 syn編譯器優化,volatile不優化
volatile具備兩種特性:
1. 保證此變量對所有線程的可見性,指一條線程修改了這個變量的值,新值對于其他線程來說是可見的,但并不是多線程安全的。
2. 禁止指令重排序優化。
Volatile如何保證內存可見性:
1.當寫一個volatile變量時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存。
2.當讀一個volatile變量時,JMM會把該線程對應的本地內存置為無效。線程接下來將從主內存中讀取共享變量。
同步:就是一個任務的完成需要依賴另外一個任務,只有等待被依賴的任務完成后,依賴任務才能完成。
異步:不需要等待被依賴的任務完成,只是通知被依賴的任務要完成什么工作,只要自己任務完成了就算完成了,被依賴的任務是否完成會通知回來。(異步的特點就是通知)。
打電話和發短信來比喻同步和異步操作。
阻塞:CPU停下來等一個慢的操作完成以后,才會接著完成其他的工作。
非阻塞:非阻塞就是在這個慢的執行時,CPU去做其他工作,等這個慢的完成后,CPU才會接著完成后續的操作。
非阻塞會造成線程切換增加,增加CPU的使用時間能不能補償系統的切換成本需要考慮。



CAS(Compare And Swap) 無鎖算法:
CAS是樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程并不會被掛起,而是被告知這次競爭中失敗,并可以再次嘗試。CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。



線程池的作用:
在程序啟動的時候就創建若干線程來響應處理,它們被稱為線程池,里面的線程叫工作線程
第一:降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
第三:提高線程的可管理性。
常用線程池:ExecutorService 是主要的實現類,其中常用的有
Executors.newSingleThreadPool(),newFixedThreadPool(),newcachedTheadPool(),newScheduledThreadPool()。

友情鏈接:線程池原理
友情鏈接:線程池原理解析


類加載器工作機制:
1.裝載:將Java二進制代碼導入jvm中,生成Class文件。
2.連接:a)校驗:檢查載入Class文件數據的正確性 b)準備:給類的靜態變量分配存儲空間 c)解析:將符號引用轉成直接引用
3:初始化:對類的靜態變量,靜態方法和靜態代碼塊執行初始化工作。
雙親委派模型:類加載器收到類加載請求,首先將請求委派給父類加載器完成
用戶自定義加載器->應用程序加載器->擴展類加載器->啟動類加載器。



一致性哈希:
Memcahed緩存:
數據結構:key,value對
使用方法:get,put等方法


Redis數據結構: String—字符串(key-value 類型)
Hash—字典(hashmap) Redis的哈希結構可以使你像在數據庫中更新一個屬性一樣只修改某一項屬性值
List—列表 實現消息隊列
Set—集合 利用唯一性
Sorted Set—有序集合 可以進行排序
可以實現數據持久化



索引:B+,B-,全文索引
MySQL的索引是一個數據結構,旨在使數據庫高效的查找數據。
常用的數據結構是B+Tree,每個葉子節點不但存放了索引鍵的相關信息還增加了指向相鄰葉子節點的指針,這樣就形成了帶有順序訪問指針的B+Tree,做這個優化的目的是提高不同區間訪問的性能。
什么時候使用索引:
1. 經常出現在group by,order by和distinc關鍵字后面的字段
2. 經常與其他表進行連接的表,在連接字段上應該建立索引
3. 經常出現在Where子句中的字段
4. 經常出現用作查詢選擇的字段



Spring IOC (控制反轉,依賴注入)
Spring支持三種依賴注入方式,分別是屬性(Setter方法)注入,構造注入和接口注入。
在Spring中,那些組成應用的主體及由Spring IOC容器所管理的對象被稱之為Bean。
Spring的IOC容器通過反射的機制實例化Bean并建立Bean之間的依賴關系。
簡單地講,Bean就是由Spring IOC容器初始化、裝配及被管理的對象。
獲取Bean對象的過程,首先通過Resource加載配置文件并啟動IOC容器,然后通過getBean方法獲取bean對象,就可以調用他的方法。
Spring Bean的作用域:
Singleton:Spring IOC容器中只有一個共享的Bean實例,一般都是Singleton作用域。
Prototype:每一個請求,會產生一個新的Bean實例。
Request:每一次http請求會產生一個新的Bean實例。



代理的共有優點:業務類只需要關注業務邏輯本身,保證了業務類的重用性。
Java靜態代理:
代理對象和目標對象實現了相同的接口,目標對象作為代理對象的一個屬性,具體接口實現中,代理對象可以在調用目標對象相應方法前后加上其他業務處理邏輯。
缺點:一個代理類只能代理一個業務類。如果業務類增加方法時,相應的代理類也要增加方法。
Java動態代理:
Java動態代理是寫一個類實現InvocationHandler接口,重寫Invoke方法,在Invoke方法可以進行增強處理的邏輯的編寫,這個公共代理類在運行的時候才能明確自己要代理的對象,同時可以實現該被代理類的方法的實現,然后在實現類方法的時候可以進行增強處理。
實際上:代理對象的方法 = 增強處理 + 被代理對象的方法
JDK和CGLIB生成動態代理類的區別:
JDK動態代理只能針對實現了接口的類生成代理(實例化一個類)。此時代理對象和目標對象實現了相同的接口,目標對象作為代理對象的一個屬性,具體接口實現中,可以在調用目標對象相應方法前后加上其他業務處理邏輯
CGLIB是針對類實現代理,主要是對指定的類生成一個子類(沒有實例化一個類),覆蓋其中的方法 。
Spring AOP應用場景
性能檢測,訪問控制,日志管理,事務等。
默認的策略是如果目標類實現接口,則使用JDK動態代理技術,如果目標對象沒有實現接口,則默認會采用CGLIB代理


SpringMVC運行原理
1. 客戶端請求提交到DispatcherServlet
2. 由DispatcherServlet控制器查詢HandlerMapping,找到并分發到指定的Controller中。
4. Controller調用業務邏輯處理后,返回ModelAndView
5. DispatcherServlet查詢一個或多個ViewResoler視圖解析器,找到ModelAndView指定的視圖
6. 視圖負責將結果顯示到客戶端



一個Http請求
DNS域名解析 –> 發起TCP的三次握手 –> 建立TCP連接后發起http請求 –> 服務器響應http請求,瀏覽器得到html代碼 –> 瀏覽器解析html代碼,并請求html代碼中的資源(如js、css、圖片等) –> 瀏覽器對頁面進行渲染呈現給用戶
設計存儲海量數據的存儲系統:設計一個叫“中間層”的一個邏輯層,在這個層,將數據庫的海量數據抓出來,做成緩存,運行在服務器的內存中,同理,當有新的數據到來,也先做成緩存,再想辦法,持久化到數據庫中,這是一個簡單的思路。主要的步驟是負載均衡,將不同用戶的請求分發到不同的處理節點上,然后先存入緩存,定時向主數據庫更新數據。讀寫的過程采用類似樂觀鎖的機制,可以一直讀(在寫數據的時候也可以),但是每次讀的時候會有個版本的標記,如果本次讀的版本低于緩存的版本,會重新讀數據,這樣的情況并不多,可以忍受。

友情鏈接:HTTP與HTTPS的區別


Session與Cookie:Cookie可以讓服務端跟蹤每個客戶端的訪問,但是每次客戶端的訪問都必須傳回這些Cookie,如果Cookie很多,則無形的增加了客戶端與服務端的數據傳輸量,
而Session則很好地解決了這個問題,同一個客戶端每次和服務端交互時,將數據存儲通過Session到服務端,不需要每次都傳回所有的Cookie值,而是傳回一個ID,每個客戶端第一次訪問服務器生成的唯一的ID,客戶端只要傳回這個ID就行了,這個ID通常為NAME為JSESSIONID的一個Cookie。這樣服務端就可以通過這個ID,來將存儲到服務端的KV值取出了。
Session和Cookie的超時問題,Cookie的安全問題


分布式Session框架
1. 配置服務器,Zookeeper集群管理服務器可以統一管理所有服務器的配置文件
2. 共享這些Session存儲在一個分布式緩存中,可以隨時寫入和讀取,而且性能要很好,如Memcache,Tair。
3. 封裝一個類繼承自HttpSession,將session存入到這個類中然后再存入分布式緩存中
4. 由于Cookie不能跨域訪問,要實現Session同步,要同步SessionID寫到不同域名下。


適配器模式:將一個接口適配到另一個接口,Java I/O中InputStreamReader將Reader類適配到InputStream,從而實現了字節流到字符流的準換。
裝飾者模式:保持原來的接口,增強原來有的功能。
FileInputStream 實現了InputStream的所有接口,BufferedInputStreams繼承自FileInputStream是具體的裝飾器實現者,將InputStream讀取的內容保存在內存中,而提高讀取的性能。


Spring事務配置方法:
1. 切點信息,用于定位實施事物切面的業務類方法
2. 控制事務行為的事務屬性,這些屬性包括事物隔離級別,事務傳播行為,超時時間,回滾規則。
Spring通過aop/tx Schema 命名空間和@Transaction注解技術來進行聲明式事物配置。


Mybatis
每一個Mybatis的應用程序都以一個SqlSessionFactory對象的實例為核心。首先用字節流通過Resource將配置文件讀入,然后通過SqlSessionFactoryBuilder().build方法創建SqlSessionFactory,然后再通過sqlSessionFactory.openSession()方法創建一個sqlSession為每一個數據庫事務服務。
經歷了Mybatis初始化 –>創建SqlSession –>運行SQL語句 返回結果三個過程


Servlet和Filter的區別:
整的流程是:Filter對用戶請求進行預處理,接著將請求交給Servlet進行處理并生成響應,最后Filter再對服務器響應進行后處理。
Filter有如下幾個用處:
Filter可以進行對特定的url請求和相應做預處理和后處理。
在HttpServletRequest到達Servlet之前,攔截客戶的HttpServletRequest。
根據需要檢查HttpServletRequest,也可以修改HttpServletRequest頭和數據。
在HttpServletResponse到達客戶端之前,攔截HttpServletResponse。
根據需要檢查HttpServletResponse,也可以修改HttpServletResponse頭和數據。
實際上Filter和Servlet極其相似,區別只是Filter不能直接對用戶生成響應。實際上Filter里doFilter()方法里的代碼就是從多個Servlet的service()方法里抽取的通用代碼,通過使用Filter可以實現更好的復用。
Filter和Servlet的生命周期:
1.Filter在web服務器啟動時初始化
2.如果某個Servlet配置了 1 ,該Servlet也是在Tomcat(Servlet容器)啟動時初始化。
3.如果Servlet沒有配置1 ,該Servlet不會在Tomcat啟動時初始化,而是在請求到來時初始化。
4.每次請求, Request都會被初始化,響應請求后,請求被銷毀。
5.Servlet初始化后,將不會隨著請求的結束而注銷。
6.關閉Tomcat時,Servlet、Filter依次被注銷。


HashMap與HashTable的區別。
1、HashMap是非線程安全的,HashTable是線程安全的。
2、HashMap的鍵和值都允許有null值存在,而HashTable則不行。
3、因為線程安全的問題,HashMap效率比HashTable的要高。
HashMap的實現機制:
1. 維護一個每個元素是一個鏈表的數組,而且鏈表中的每個節點是一個Entry[]鍵值對的數據結構。
2. 實現了數組+鏈表的特性,查找快,插入刪除也快。
3. 對于每個key,他對應的數組索引下標是 int i = hash(key.hashcode)&(len-1);
4. 每個新加入的節點放在鏈表首,然后該新加入的節點指向原鏈表首

HashMap和TreeMap區別
HashMap沖突
友情鏈接:HashMap的工作原理


HashMap,ConcurrentHashMap與LinkedHashMap的區別

  • ConcurrentHashMap是使用了鎖分段技術技術來保證線程安全的,鎖分段技術:首先將數據分成一段一段的存儲,然后給每一段數據配一把鎖,當一個線程占用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問
  • ConcurrentHashMap 是在每個段(segment)中線程安全的
  • LinkedHashMap維護一個雙鏈表,可以將里面的數據按寫入的順序讀出

ConcurrentHashMap應用場景
1:ConcurrentHashMap的應用場景是高并發,但是并不能保證線程安全,而同步的HashMap和HashMap的是鎖住整個容器,而加鎖之后ConcurrentHashMap不需要鎖住整個容器,只需要鎖住對應的Segment就好了,所以可以保證高并發同步訪問,提升了效率。
2:可以多線程寫。
ConcurrentHashMap把HashMap分成若干個Segmenet
1.get時,不加鎖,先定位到segment然后在找到頭結點進行讀取操作。而value是volatile變量,所以可以保證在競爭條件時保證讀取最新的值,如果讀到的value是null,則可能正在修改,那么久調用ReadValueUnderLock函數,加鎖保證讀到的數據是正確的。
2.Put時會加鎖,一律添加到hash鏈的頭部。
3.Remove時也會加鎖,由于next是final類型不可改變,所以必須把刪除的節點之前的節點都復制一遍。
4.ConcurrentHashMap允許多個修改操作并發進行,其關鍵在于使用了鎖分離技術。它使用了多個鎖來控制對Hash表的不同Segment進行的修改。
ConcurrentHashMap的應用場景是高并發,但是并不能保證線程安全,而同步的HashMap和HashTable的是鎖住整個容器,而加鎖之后ConcurrentHashMap不需要鎖住整個容器,只需要鎖住對應的segment就好了,所以可以保證高并發同步訪問,提升了效率。



Vector和ArrayList的區別


ExecutorService service = Executors….
ExecutorService service = new ThreadPoolExecutor()
ExecutorService service = new ScheduledThreadPoolExecutor();
ThreadPoolExecutor源碼分析
線程池本身的狀態:
等待任務隊列和工作集:
線程池的主要狀態鎖:
線程池的存活時間和大小:
1.2 ThreadPoolExecutor 的內部工作原理
有了以上定義好的數據,下面來看看內部是如何實現的 。 Doug Lea 的整個思路總結起來就是 5 句話:
1. 如果當前池大小 poolSize 小于 corePoolSize ,則創建新線程執行任務。
2. 如果當前池大小 poolSize 大于 corePoolSize ,且等待隊列未滿,則進入等待隊列
3. 如果當前池大小 poolSize 大于 corePoolSize 且小于 maximumPoolSize ,且等待隊列已滿,則創建新線程執行任務。
4. 如果當前池大小 poolSize 大于 corePoolSize 且大于 maximumPoolSize ,且等待隊列已滿,則調用拒絕策略來處理該任務。
5. 線程池里的每個線程執行完任務后不會立刻退出,而是會去檢查下等待隊列里是否還有線程任務需要執行,如果在 keepAliveTime 里等不到新的任務了,那么線程就會退出。
Executor包結構
CopyOnWriteArrayList : 寫時加鎖,當添加一個元素的時候,將原來的容器進行copy,復制出一個新的容器,然后在新的容器里面寫,寫完之后再將原容器的引用指向新的容器,而讀的時候是讀舊容器的數據,所以可以進行并發的讀,但這是一種弱一致性的策略。
使用場景:CopyOnWriteArrayList適合使用在讀操作遠遠大于寫操作的場景里,比如緩存。


Linux常用命令:cd,cp,mv,rm,ps(進程),tar,cat(查看內容),chmod,vim,find,ls

死鎖的必要條件
1. 互斥 至少有一個資源處于非共享狀態
2. 占有并等待
3. 非搶占
4. 循環等待
解決死鎖,第一個是死鎖預防,就是不讓上面的四個條件同時成立。二是,合理分配資源。
三是使用銀行家算法,如果該進程請求的資源操作系統剩余量可以滿足,那么就分配。


進程間的通信方式
  • 管道( pipe ):管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關系的進程間使用。進程的親緣關系通常是指父子進程關系。
  • 有名管道 (named pipe) : 有名管道也是半雙工的通信方式,但是它允許無親緣關系進程間的通信。
    3.信號量( semophore ) : 信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作為一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作為進程間以及同一進程內不同線程之間的同步手段。
  • 消息隊列( message queue ) : 消息隊列是由消息的鏈表,存放在內核中并由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩沖區大小受限等缺點。
    5.信號 ( sinal ) : 信號是一種比較復雜的通信方式,用于通知接收進程某個事件已經發生。
    6.共享內存( shared memory ) :共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號量,配合使用,來實現進程間的同步和通信。
    7.套接字( socket ) : 套解口也是一種進程間通信機制,與其他通信機制不同的是,它可用于不同機器間的進程通信。


數據庫事務是指作為單個邏輯工作單元執行的一系列操作。


hibernate的一級緩存是由Session提供的,因此它只存在于Session的生命周期中,當程序調用save(),update(),saveOrUpdate()等方法 及調用查詢接口list,filter,iterate時,如Session緩存中還不存在相應的對象,Hibernate會把該對象加入到一級緩存中,當Session關閉的時候緩存也會消失。
Hibernate的一級緩存是Session所內置的,不能被卸載,也不能進行任何配置一級緩存采用的是key-value的Map方式來實現的,在緩存實體對象時,對象的主關鍵字ID是Map的key,實體對象就是對應的值。
Hibernate二級緩存:把獲得的所有數據對象根據ID放入到第二級緩存中。Hibernate二級緩存策略,是針對于ID查詢的緩存策略,刪除、更新、增加數據的時候,同時更新緩存。

具體更多資源可前往Java后端面試總結




回復

使用道具 舉報