2019年JAVA開發工程師面試題系列一

1、spring 是如何創建bean的?

在IoC容器中,bean的獲取主要通過BeanFactory和ApplicationContext獲取,這裏ApplicationContext實際上是繼承自BeanFactory的,兩者的區別在於BeanFactory對bean的初始化主要是延遲初始化的方式,而ApplicationContext對bean的初始化是在容器啓動時即將所有bean初始化完畢。

2、spring bean的作用域?

Spring IOC容器創建一個Bean實例時,可以爲Bean指定實例的作用域,作用域包括singleton(單例模式)、prototype(原型模式)、request(HTTP請求)、session(會話)、global-session(全局會話)

單例(singleton):它是默認的選項,在整個應用中,Spring只爲其生成一個Bean的實例。

原型(prototype):當每次注入,或者通過Spring IoC容器獲取Bean時,Spring都會爲它創建一個新的實例。

會話(session):在Web應用中使用,就是在會話過程中Spring只創建一個實例。

請求(request):在Web應用中使用的,就是在一次請求中Spring會創建一個實例,但是不同的請求會創建不同的實例。

全局會話(global-session):全局會話內有效,假如你在編寫一個標準的基於Servlet的web應用,並且定義了一個或多個具有global session作用域的bean,系統會使用標準的HTTP Session作用域,並且不會引起任何錯誤。

3、spring事務的傳播級別?

事務特性(4種):

原子性 (atomicity):強調事務的不可分割.

一致性 (consistency):事務的執行的前後數據的完整性保持一致.

隔離性 (isolation):一個事務執行的過程中,不應該受到其他事務的干擾

持久性(durability) :事務一旦結束,數據就持久到數據庫

4、髒讀、不可重複讀、幻讀

髒讀 :髒讀就是指當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然後使用了這個數據。

不可重複讀 :是指在一個事務內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。那麼,在第一個事務中的兩 次讀數據之間,由於第二個事務的修改,那麼第一個事務兩次讀到的的數據可能是不一樣的。這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱爲是不 可重複讀。

虛幻讀 :是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。 同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那麼,以後就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象 發生了幻覺一樣。.

5、設置事務隔離級別(5種)

DEFAULT 這是一個PlatfromTransactionManager默認的隔離級別,使用數據庫默認的事務隔離級別.

未提交讀(read uncommited) :髒讀,不可重複讀,虛讀都有可能發生

已提交讀 (read commited):避免髒讀。但是不可重複讀和虛讀有可能發生

可重複讀 (repeatable read) :避免髒讀和不可重複讀.但是虛讀有可能發生.

串行化的 (serializable) :避免以上所有讀問題.

Mysql 默認:可重複讀

Oracle 默認:讀已提交

read uncommited:是最低的事務隔離級別,它允許另外一個事務可以看到這個事務未提交的數據。

read commited:保證一個事物提交後才能被另外一個事務讀取。另外一個事務不能讀取該事物未提交的數據。

repeatable read:這種事務隔離級別可以防止髒讀,不可重複讀。但是可能會出現幻象讀。它除了保證一個事務不能被另外一個事務讀取未提交的數據之外還避免了以下情況產生(不可重複讀)。

serializable:這是花費最高代價但最可靠的事務隔離級別。事務被處理爲順序執行。除了防止髒讀,不可重複讀之外,還避免了幻象讀(避免三種)。

6、事務的傳播行爲 注意是大寫

* 保證同一個事務中

Propagation_required:PROPAGATION_REQUIRED 支持當前事務,如果不存在 就新建一個(默認)

Propagation_supports:PROPAGATION_SUPPORTS 支持當前事務,如果不存在,就不使用事務

Propagation_mandatory:PROPAGATION_MANDATORY 支持當前事務,如果不存在,拋出異常

* 保證沒有在同一個事務中

Propagation_requires_new:PROPAGATION_REQUIRES_NEW 如果有事務存在,掛起當前事務,創建一個新的事務

Propagation_not_supported:PROPAGATION_NOT_SUPPORTED 以非事務方式運行,如果有事務存在,掛起當前事務

Propagation_never:PROPAGATION_NEVER 以非事務方式運行,如果有事務存在,拋出異常

Propagation_nested:PROPAGATION_NESTED 如果當前事務存在,則嵌套事務執行

7、Transactional 註解

@Transactional 註解只能應用到 public 方法纔有效。

如果不生效,加上@Transactional(rollbackFor = Exception.class)

原因是:當我們使用@Transaction 時默認爲RuntimeException(也就是運行時異常)異常纔會回滾。

8、spring boot註解?

@SpringBootApplication:申明讓spring boot自動給程序進行必要的配置,這個配置等同於:

@Configuration ,@EnableAutoConfiguration 和 @ComponentScan 三個配置。

@ComponentScan:讓spring Boot掃描到Configuration類並把它加入到程序上下文。

@Configuration :等同於spring的XML配置文件;使用Java代碼可以檢查類型安全。

@EnableAutoConfiguration :自動配置。

9、spring Boot是如何實現自動配置的?

1)@SpringBootApplication註解主配置類裏邊最主要的功能就是SpringBoot開啓了一個@EnableAutoConfiguration註解的自動配置功能。

2)@EnableAutoConfiguration(開啓自動配置)作用:它主要利用了一個EnableAutoConfigurationImportSelector選擇器給Spring容器中來導入一些組件。

3)開啓自動配置導入選擇器調用、selectImports()方法通過SpringFactoriesLoader.loadFactoryNames()掃描所有具有META-INF/spring.factories的jar包。

4)這個spring.factories文件也是一組一組的key=value的形式,其中一個key是X類的全類名,而它的value是一個X的類名的列表,這些類名以逗號分隔.pring.factories文件,則是用來記錄項目包外需要註冊的bean類名。

5)這個@EnableAutoConfiguration註解通過@SpringBootApplication被間接的標記在了Spring Boot的啓動類上。在SpringApplication.run(...)的內部就會執行selectImports()方法,找到所有JavaConfig自動配置類的全限定名對應的class,然後將所有自動配置類加載到Spring容器中。

10、索引

數據庫索引,是數據庫管理系統中一個排序的數據結構,以協助快速查詢、更新數據庫表中數據。採取的是空間換時間的概念。

MyISAM引擎和InnoDB引擎使用B+Tree作爲索引結構

11、mysql索引?

1)普通索引,這是最基本的索引,它沒有任何限制,比如上文中爲title字段創建的索引就是一個普通索引,MyIASM中默認的BTREE類型的索引,也是我們大多數情況下用到的索引。

2)唯一索引,與普通索引類似,不同的就是:索引列的值必須唯一,但允許有空值(注意和主鍵不同)。如果是組合索引,則列值的組合必須唯一,創建方法和普通索引類似。

3)全文索引(FULLTEXT),對於較大的數據集,將你的資料輸入一個沒有FULLTEXT索引的表中,然後創建索引,其速度比把資料輸入現有FULLTEXT索引的速度更爲快。不過切記對於大容量的數據表,生成全文索引是一個非常消耗時間非常消耗硬盤空間的做法。

4)單列索引、多列索引,多個單列索引與單個多列索引的查詢效果不同,因爲執行查詢時,MySQL只能使用一個索引,會從多個索引中選擇一個限制最爲嚴格的索引。

5)組合索引(最左前綴),平時用的SQL查詢語句一般都有比較多的限制條件,所以爲了進一步榨取MySQL的效率,就要考慮建立組合索引

12、什麼是最左前綴原則?

 比如字段a,b,c建立複合索引。

 where a=1 and c=4 and b=10         可以利用到索引 (a,b,c),即使順序亂也可以

 where a=1可以利用到索引 (a,b,c)

 where b=5無法利用索引 (a,b,c)

13、mysql的存儲引擎

1)MyISAM 不支持事務,不支持外鍵,優勢是訪問速度快,對事務完整性沒有要求,或者以select、insert爲主的可以使用

2)InnoDB 支持事務,外鍵約束,自增,寫的效率差一些,更佔據空間,支持行級鎖

3)Memory 使用內存中的內容來創建表,訪問速度非常快,使用哈希索引。但是一旦服務關閉,表中的數據就會丟失。

4)Merge 是一組MyISAM表的組合,這些表必須結構完全相同,merge本身沒有數據。對merge的查詢、更新、刪除實際是對MyISAM的修改。

14、存儲引擎 MyISAM和InnoDB區別:

1)InnoDB支持事務,MyISAM不支持。

2)MyISAM適合查詢以及插入爲主的應用,InnoDB適合頻繁修改以及涉及到安全性較高的應用。

3)InnoDB支持外鍵,MyISAM不支持。

4)從MySQL5.5.5以後,InnoDB是默認引擎。

5)MyISAM支持全文類型索引,而InnoDB不支持全文索引。

6)InnoDB中不保存表的總行數,select count(*) from table時,InnoDB需要掃描整個表計算有多少行,但MyISAM只需簡單讀出保存好的總行數即可。注:當count(*)語句包含where條件時MyISAM也需掃描整個表。

7)對於自增長的字段,InnoDB中必須包含只有該字段的索引,但是在MyISAM表中可以和其他字段一起建立聯合索引。

8)清空整個表時,InnoDB是一行一行的刪除,效率非常慢。MyISAM則會重建表。MyisAM使用delete語句刪除後並不會立刻清理磁盤空間,需要定時清理,命令:OPTIMIZE table dept;

9)InnoDB支持行鎖(某些情況下還是鎖整表,如 update table set a=1 where user like ‘%lee%’)

10)Myisam創建表生成三個文件:.frm 數據表結構 、 .myd 數據文件 、 .myi 索引文件,Innodb只生成一個 .frm文件,數據存放在ibdata1.log

現在一般都選用InnoDB,主要是MyISAM的全表鎖,讀寫串行問題,併發效率鎖表,效率低,MyISAM對於讀寫密集型應用一般是不會去選用的。

應用場景:

MyISAM不支持事務處理等高級功能,但它提供高速存儲和檢索,以及全文搜索能力。如果應用中需要執行大量的SELECT查詢,那麼MyISAM是更好的選擇。

InnoDB用於需要事務處理的應用程序,包括ACID事務支持。如果應用中需要執行大量的INSERT或UPDATE操作,則應該使用InnoDB,這樣可以提高多用戶併發操作的性能。

15、數據庫查詢優化

1)避免全部掃描,比如對null值進行篩選判讀;使用!=或<>、like、or等等都將放棄索引全表掃描

2)考慮在where及order by涉及的列上建立索引

3)使用正向邏輯(not in,not exists)

4)數據庫不擅長運算,把運算交給邏輯代碼,非要有把運算放在右邊

5)合理建表,使用合理的字段,善用非空、外鍵約束保證數據的完整性

6)索引並不是越多越好,一個表最好不要超過6個,多了影響增、刪、改的性能。這個影響很大

7)多從業務邏輯方面考慮問題,合理使用中間件

8)對於數據量太大的數據分庫分表,使用中間件比如mycat

252、分表分庫

①:垂直分割(並不常用)

就是將一個表按照字段來分,每張表保證有相同的主鍵就好。一般來說,將常用字段和大字段分表來放。

優勢:比沒有分表來說,提高了查詢速度,降低了查詢結果所用內存;

劣勢:沒有解決大量記錄的問題,對於單表來說隨着記錄增多,性能還是下降很快;

②: 水平分割(重要,實際應用中使用最多)

水平分割是企業最常用到的,水平拆分就是大表按照記錄分爲很多子表:

水平分的規則完全是自定義的,有以下幾種參考設計:

1 hash、自增id取模:

對某個字段進行hash來確定創建幾張表,並根據hash結果存入不同的表;

2 按時間

根據業務可以按照天、月、年來進行拆分;

3 按每個表的固定記錄數

一般按照自增ID進行拆表,一張表的數據行到了指定的數量,就自動保存到下一張表中。比如規定一張表只能存1-1000個記錄;

4 將老數據遷移到一張歷史表

比如日誌表,一般只查詢3個月之內的數據,對於超過3個月的記錄將之遷移到歷史子表中;

16、    Sql執行順序:

(1)FROM [left_table]

(2)ON <join_condition>

(3)<join_type> JOIN <right_table>

(4)WHERE <where_condition>

(5)GROUP BY <group_by_list>

(6)WITH <CUBE | RollUP>

(7)HAVING <having_condition>

(8)SELECT

(9)DISTINCT 

(10)ORDER BY <order_by_list>

(11)<Top Num> <select list>

GROUP BY表示分組,按某一個字段進行分組

HAVING是對於GROUP BY對象進行篩選

17、MySQL中exists和in的區別及使用場景

外層查詢表小於子查詢表,則用exists,外層查詢表大於子查詢表,則用in,如果外層和子查詢表差不多,則愛用哪個用哪個

18、Redis支持的數據類型?

1)St ing字符串:格式: set key value

string類型是二進制安全的。意思是redis的string可以包含任何數據。比如jpg圖片或者序列化的對象 。string類型是Redis最基本的數據類型,一個鍵最大能存儲512MB。

2)Hash(哈希)格式: hmset name  key1 value1 key2 value2

Redis hash 是一個鍵值(key=>value)對集合。Redis hash是一個string類型的field和value的映射表,hash特別適合用於存儲對象。

3)    lsit(列表)Redis 列表是簡單的字符串列表,按照插入順序排序。你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)

4)set(集合)

5)zset(有序集合)

Redis zset 和 set 一樣也是string類型元素的集合,且不允許重複的成員。

不同的是每個元素都會關聯一個double類型的分數。redis正是通過分數來爲集合中的成員進行從小到大的排序。

zset的成員是唯一的,但分數(score)卻可以重複。

19、什麼是Redis持久化?Redis有哪幾種持久化方式?優缺點是什麼?

持久化就是把內存的數據寫到磁盤中去,防止服務宕機了內存數據丟失。

Redis 提供了兩種持久化方式:RDB(默認) 和AOF

RDB:rdb是Redis DataBase縮寫

功能核心函數rdbSave(生成RDB文件)和rdbLoad(從文件加載內存)兩個函數

AOF:Aof是Append-only file縮寫

每當執行服務器(定時)任務或者函數時flushAppendOnlyFile 函數都會被調用, 這個函數執行以下兩個工作

aof寫入保存:

WRITE:根據條件,將 aof_buf 中的緩存寫入到 AOF 文件

SAVE:根據條件,調用 fsync 或 fdatasync 函數,將 AOF 文件保存到磁盤中。

比較:

1、aof文件比rdb更新頻率高,優先使用aof還原數據。

2、aof比rdb更安全也更大

3、rdb性能比aof好

4、如果兩個都配了優先加載AOF

20、redis緩存失效的解決方案?

1)首先redis是默認永不過期的,如果要手動設置時間,需要增量設置過期時間,避免redis中的緩存在同一時間失效。如果是系統級別的原因,比如宕機,採用主從複製。

2)緩存穿透的問題,通過設置分佈式鎖,取得鎖的進程操作數據庫並更新緩存,沒取得鎖的進程發現有鎖就等待。

21、如何實現redis分佈式鎖?

1)加鎖操作:jedis.set(key,value,"NX","EX",timeOut)。

key就是redis的key值作爲鎖的標識,value在這裏作爲客戶端的標識,只有key-value都比配纔有刪除鎖的權利【保證安全性】

NX:只有這個key不存才的時候纔會進行操作,if not exists;

EX:設置key的過期時間爲秒,具體時間由第5個參數決定

通過timeOut設置過期時間保證不會出現死鎖【避免死鎖】

2)解鎖操作:unLock(String key,String value)

執行一個lua腳本,如果根據key拿到的value跟傳入的value相同就執行del,否則就返回

3)重試機制:lockRetry(String key,String value,Long timeOut,Integer retry,Long sleepTime)

主要是用於其他進程,如果沒有發現有鎖就進入睡眠狀態,設置睡眠時間,以及重試次數(循環次數)

22、如何組織表單提交!

jS做一個狀態碼false,當提交成功後狀態碼爲true,提交前先驗證這個狀態碼是否爲false,否則就返回

23、微信小程序的四個組件

WXML,WXSS,javascript,json

24、消息中間件

消息中間件是程序相互通信的一種方式,消息隊列是消息中間件的一種實現方式。

25、中間件消息丟失的解決方法?

1)消息沒有收到,使用事務(有這個註解),接收到消息就返回一個狀態,否則重複發送。性能會降低,類似於微信支付寶支付的異步通知。

2)MQ保存消息丟失,這種情況基本不存在。AMQ是一種文件存儲形式,它具有寫入速度快和容易恢復的特點。消息存儲在一個個文件中,文件的默認大小爲32M,如果一條消息的大小超過了32M,那麼這個值必須設置大一點。當一個存儲文件中的消息已經全部被消費,那麼這個文件將被標識爲可刪除,在下一個清除階段,這個文件被刪除。

如果要消息記錄可以考慮持久化到數據庫中

26、dubbo採用的是什麼通信協議?

dubbo支持不同的通信協議

1)dubbo協議

dubbo://192.168.0.1:20188

默認就是走dubbo協議的,單一長連接,NIO異步通信,基於hessian作爲序列化協議(默認)

適用的場景就是:傳輸數據量很小(每次請求在100kb以內),但是併發量很高

爲了要支持高併發場景,一般是服務提供者就幾臺機器,但是服務消費者有上百臺,可能每天調用量達到上億次!此時用長連接是最合適的,就是跟每個服務消費者維持一個長連接就可以,可能總共就100個連接。然後後面直接基於長連接NIO異步通信,可以支撐高併發請求。

否則如果上億次請求每次都是短連接的話,服務提供者會扛不住。

而且因爲走的是單一長連接,所以傳輸數據量太大的話,會導致併發能力降低。所以一般建議是傳輸數據量很小,支撐高併發訪問。

2)rmi協議

走java二進制序列化,多個短連接,適合消費者和提供者數量差不多,適用於文件的傳輸,一般較少用

3)hessian協議

走hessian序列化協議,多個短連接,適用於提供者數量比消費者數量還多,適用於文件的傳輸,一般較少用

4)http協議

走json序列化

5)webservice

走SOAP文本序列化

27、dubbo的運行過程

1)服務提供者在啓動時,向服務註冊中心註冊自己提供的服務

2)服務消費者在啓動時,向註冊中心訂閱自己所需要的服務

3)註冊中心返回服務提供者地址列表給服務消費者。如果有變更,服務註冊中心將使用長連接推送變更數據給消費者

4)服務消費者,從服務提供者地址列表中,基於軟負載均衡算法,選一臺提供者進行調用。如果調用失敗,再選另一臺調用

5) 服務消費者和服務提供者,在內存中累計調用次數和調用時間,定時每分鐘發送一次數據到監控中心

28、zookeeper的選舉機制?

zookeeper提供了三種方式:

LeaderElection,AuthFastLeaderElection(授權快速領導人選舉),FastLeaderElection

默認的算法是FastLeaderElection(快速選擇領導),所以主要分析它的選舉機制。

當啓動初始化集羣的時候,server1的myid爲1,zxid爲0   server2的myid爲2,zxid同樣是0,以此類推。此種情況下zxid都是爲0。先比較zxid,再比較myid服務器1啓動,給自己投票,然後發投票信息,由於其它機器還沒有啓動所以它收不到反饋信息,服務器1的狀態一直屬於Looking(選舉狀態)。

服務器2啓動,給自己投票,同時與之前啓動的服務器1交換結果,由於服務器2的myid大所以服務器2勝出,但此時投票數沒有大於半數,所以兩個服務器的狀態依然是LOOKING。

服務器3啓動,給自己投票,同時與之前啓動的服務器1,2交換信息,由於服務器3的myid最大所以服務器3勝出,此時投票數正好大於半數,所以服務器3成爲領導者,服務器1,2成爲小弟。

服務器4啓動,給自己投票,同時與之前啓動的服務器1,2,3交換信息,儘管服務器4的myid大,但之前服務器3已經勝出,所以服務器4只能成爲小弟。

服務器5啓動,後面的邏輯同服務器4成爲小弟

當選舉機器過半的時候,已經選舉出leader後,後面的就跟隨已經選出的leader,所以4和5跟隨成爲leader的server3

所以,在初始化的時候,一般到過半的機器數的時候誰的myid最大一般就是leader

運行期間

按照上述初始化的情況,server3成爲了leader,在運行期間處於leader的server3掛了,那麼非Observer服務器server1、server2、server4、server5會將自己的節點狀態變爲LOOKING狀態

1、開始進行leader選舉。現在選舉同樣是根據myid和zxid來進行

2、首先每個server都會給自己投一票競選leader。假設server1的zxid爲123,server2的zxid爲124,server4的zxid爲169,server5的zxid爲188

3、同樣先是比較zxid再比較,server1、server2、server4比較server4根據優先條件選舉爲leader。然後server5還是跟隨server4,即使server5的zxid最大,但是當選舉到server4的時候,機器數已經過半。不再進行選舉,跟隨已經選舉的leader

zookeeper集羣爲保證數據的一致性所有的操作都是由leader完成,之後再由leader同步給follower。重點就在這兒,zookeeper並不會確保所有節點都同步完數據,只要有大多數節點(即n/2+1)同步成功即可。

咱們假設有一個寫操作成功那麼現在數據只存在於節點leader,之後leader再同步給其他follower。這時候宕掉3個機器,已經過半的機器無法進行投票選舉,剩餘2臺不足過半,無法選舉=無法提供任何服務。再啓動一個機器恢復服務。所以宕掉的機器不要過半,過半就會導致無法正常服務

29、springMVC核心類

1)制器核心類:

org.springframework.web.servlet.DispatcherServlet  - 配置web.xml

2)加載配置文件核心類:

org.springframework.web.context.ContextLoaderListener – spring的配置文件

3)處理url影射核心類:

org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping-根據bean的名稱請求一個bean. spring的配置文件- /abc

4)處理視圖資源核心類:

org.springframework.web.servlet.view.ResourceBundleViewResolver

5)方法動態調用核心類

org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver

30、什麼是雙向鏈表?

雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數據結點中都有兩個指針,分別指向直接後繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。

31、Lock與synchronized有以下區別:

1)Lock是一個接口,而synchronized是關鍵字。

2)synchronized會自動釋放鎖,而Lock必須手動釋放鎖。

3)Lock可以讓等待鎖的線程響應中斷,而synchronized不會,線程會一直等待下去。

4)通過Lock可以知道線程有沒有拿到鎖,而synchronized不能。

5)Lock能提高多個線程讀操作的效率。

6)synchronized能鎖住類、方法和代碼塊,而Lock是塊範圍內的

32、HashMap 的長度爲什麼是2的冪次方

爲了能讓 HashMap 存取高效,儘量較少碰撞,也就是要儘量把數據分配均勻,每個鏈表/紅黑樹長度大致相同。這個實現就是把數據存到哪個鏈表/紅黑樹中的算法。

32、shiro登錄的執行流程

創建 AuthenticationToken,然後調用 Subject.login 方法進行登錄認證;

Subject 委託給 SecurityManager;

SecurityManager 委託給 Authenticator 接口;

Authenticator 接口調用 Realm 獲取登錄信息。

33、Shiro 的三大核心組件:

1、Subject :當前用戶的操作

2、SecurityManager:用於管理所有的Subject

3、Realms:用於進行權限信息的驗證,認證授權都在這裏

33、身份認證Authentication 和 授權Authorization

在shiro的用戶權限認證過程中其通過兩個方法來實現:

1、Authentication:是驗證用戶身份的過程。重寫doGetAuthenticationInfo()方法

2、Authorization:是授權訪問控制,用於對用戶進行的操作進行人證授權,證明該用戶是否允許進行當前操作,如訪問某個鏈接,某個資源文件等,重寫doGetAuthorizationInfo()

3、實現權限is or需要重寫AuthorizationFilter中的isAccessAllowed(),循環遍歷角色的權限數組,只要包含其一就返回true

其他組件:

除了以上幾個組件外,Shiro還有幾個其他組件:

1、SessionManager :Shiro爲任何應用提供了一個會話編程範式。

2、CacheManager :對Shiro的其他組件提供緩存支持。

4、remenberMe:記住我

34、爲什麼說Mybatis是半自動ORM映射工具?它與全自動的區別在哪裏?

Hibernate屬於全自動ORM映射工具,使用Hibernate查詢關聯對象或者關聯集合對象時,可以根據對象關係模型直接獲取,所以它是全自動的。而Mybatis在查詢關聯對象或關聯集合對象時,需要手動編寫sql來完成,所以,稱之爲半自動ORM映射工具。

35、 一對一、一對多的關聯查詢 ? 

association 一對一, 一對多  collection,多對多 discrimination

36、MyBatis實現一對一有幾種方式?具體怎麼操作的?

有聯合查詢和嵌套查詢,聯合查詢是幾個表聯合查詢,只查詢一次, 通過在resultMap裏面配置association節點配置一對一的類就可以完成;

嵌套查詢是先查一個表,根據這個表裏面的結果的 外鍵id,去再另外一個表裏面查詢數據,也是通過association配置,但另外一個表的查詢通過select屬性配置。

37、MyBatis實現一對多有幾種方式,怎麼操作的?

有聯合查詢和嵌套查詢。聯合查詢是幾個表聯合查詢,只查詢一次,通過在resultMap裏面的collection節點配置一對多的類就可以完成;嵌套查詢是先查一個表,根據這個表裏面的 結果的外鍵id,去再另外一個表裏面查詢數據,也是通過配置collection,但另外一個表的查詢通過select節點配置。

38、Mybatis是否支持延遲加載?如果支持,它的實現原理是什麼?

Mybatis僅支持association關聯對象和collection關聯集合對象的延遲加載,association指的就是一對一,collection指的就是一對多查詢。在Mybatis配置文件中,可以配置是否啓用延遲加載lazyLoadingEnabled=true|false。

它的原理是,使用CGLIB創建目標對象的代理對象,當調用目標方法時,進入攔截器方法,比如調用a.getB().getName(),攔截器invoke()方法發現a.getB()是null值,那麼就會單獨發送事先保存好的查詢關聯B對象的sql,把B查詢上來,然後調用a.setB(b),於是a的對象b屬性就有值了,接着完成a.getB().getName()方法的調用。這就是延遲加載的基本原理。

當然了,不光是Mybatis,幾乎所有的包括Hibernate,支持延遲加載的原理都是一樣的。

 39、Mybatis的一級、二級緩存:

1)一級緩存: 基於 PerpetualCache 的 HashMap 本地緩存,其存儲作用域爲 Session,當 Session flush 或 close 之後,該 Session 中的所有 Cache 就將清空,默認打開一級緩存。

2)二級緩存與一級緩存其機制相同,默認也是採用 PerpetualCache,HashMap 存儲,不同在於其存儲作用域爲 Mapper(Namespace),並且可自定義存儲源,如 Ehcache。默認不打開二級緩存,要開啓二級緩存,使用二級緩存屬性類需要實現Serializable序列化接口(可用來保存對象的狀態),可在它的映射文件中配置<cache/> ;

3)對於緩存數據更新機制,當某一個作用域(一級緩存 Session/二級緩存Namespaces)的進行了C/U/D 操作後,默認該作用域下所有 select 中的緩存將被 clear 掉並重新更新,如果開啓了二級緩存,則只根據配置判斷是否刷新。

40 、Hibernate的緩存機制?

一級緩存

  session級別的緩存,當我們使用了get load find Query等查詢出來的數據,默認在session中就會有一份緩存數據,緩存數據就是從數據庫將一些數據拷貝一份放到對應的地方.

  一級緩存不可卸載: (只要使用了session,肯定用到了session的緩存機制,是hibernate控制的,我們不能手動配置)

一級緩存的清理:

   close clear這兩種方式會全部清理; evict方法是將指定的緩存清理掉

二級緩存

  sessionFactory級別的緩存,可以做到多個session共享此信息

sessionFactory緩存分類:

1. 內緩存: 預製的sql語句,對象和數據庫的映射信息

2. 外緩存:存儲的是我們允許使用二級緩存的對象

適合放在二級緩存中的數據:

1. 經常被修改的數據

2. 不是很想重要的數據,允許出現偶爾併發的數據

3. 不會被併發訪問的數據

4. 參考數據

適合放到一級緩存中的數據:

1. 經常被修改的數據

2. 財務數據,絕對不允許出現併發

3. 與其它應用共享的數據

Hibernate的二級緩存策略的一般過程:

1. 條件查詢的時候,

    String hql = “from 類名”;

這樣的SQL語句查詢數據庫,一次獲得所有的數據庫.

2.把獲得的所有數據對象根據ID放入到第二級緩存中

3.當Hibernate根據ID訪問數據對象的時候,首先從Session一級緩存中查;查不到,如果配置了二級緩存,那麼從二級緩存中查;查不到,再查詢數據庫,把結果按照ID放入到緩存

4.刪除 更新 增加數據的時候,同時更新緩存

注: Hibernate的二級緩存策略,是針對於ID查詢的緩存策略,對於條件查詢則毫無作用.爲此,Hibernate提供了針對條件查詢的Query緩存

41、springboot啓動流程

1.創建SpringApplication實例

1)  在SpringApplicaiton構造器中調用initialize(sources)方法。initialize方法中,將sources轉換成list加到this.sources屬性中。

2) 判斷是否爲web環境,在類路徑下是否可以加載到Servlet和ConfigurableWebApplicationContext

3) 設置初始化器,從META-INF/spring.factories處讀取配置文件中Key爲:org.springframework.context.ApplicationContextInitializer的value,進行實例化操作 

4) 設置監聽器,StopWatch主要是監控啓動過程,統計啓動時間,檢測應用是否已經啓動或者停止。

5) 推斷應用入口類,通過尋找main方法找到啓動主類。

2.執行SpringApplication.run()

1) 獲取SpringApplicationRunListeners,(也是通過META-INF/spring.factories),默認加載的是EventPublishingRunListener。啓動監聽,調用RunListener.starting()方法。

2) 根據SpringApplicationRunListeners以及參數來準備環境,獲取環境變量environment,將應用參數放入到環境變量持有對象中,監聽器監聽環境變量對象的變化(listener.environmentPrepared),打印Banner信息(SpringBootBanner)

3) 創建ApplicationContext(spring上下文AnnotationConfigEmbeddedWebApplicationContext)

4) 創建FailureAnalyzer, 用於觸發從spring.factories加載的FailureAnalyzer和FailureAnalysisReporter實例

5) spring上下文前置處理prepareContext

6) spring上下文刷新refreshContext

7) spring上下文後置處理afterRefresh(ApplicationRunner,CommandLineRunner接口實現類的啓動),返回上下文對象

42、java類加載過程

類加載的過程主要分爲三個部分:加載;鏈接;初始化

而鏈接又可以細分爲三個小部分:驗證;準備;解析

1)加載

簡單來說,加載指的是把class字節碼文件從各個來源通過類加載器裝載入內存中。

這裏有兩個重點:

字節碼來源:一般的加載來源包括從本地路徑下編譯生成的.class文件,從jar包中的.class文件,從遠程網絡,以及動態代理實時編譯

類加載器:一般包括啓動類加載器,擴展類加載器,應用類加載器,以及用戶的自定義類加載器。

2)鏈接

驗證:主要是爲了保證加載進來的字節流符合虛擬機規範,不會造成安全錯誤。

包括對於文件格式的驗證,比如常量中是否有不被支持的常量?文件中是否有不規範的或者附加的其他信息?對於元數據的驗證,比如該類是否繼承了被final修飾的類?類中的字段,方法是否與父類衝突?是否出現了不合理的重載?

對於字節碼的驗證,保證程序語義的合理性,比如要保證類型轉換的合理性。

對於符號引用的驗證,比如校驗符號引用中通過全限定名是否能夠找到對應的類?校驗符號引用中的訪問性(private,public等)是否可被當前類訪問?

準備:主要是爲類變量(注意,不是實例變量)分配內存,並且賦予初值。

特別需要注意,初值,不是代碼中具體寫的初始化的值,而是Java虛擬機根據不同變量類型的默認初始值。比如8種基本類型的初值,默認爲0;引用類型的初值則爲null;常量的初值即爲代碼中設置的值,final static tmp = 456, 那麼該階段tmp的初值就是456

解析:將常量池內的符號引用替換爲直接引用的過程。

兩個重點:

符號引用。即一個字符串,但是這個字符串給出了一些能夠唯一性識別一個方法,一個變量,一個類的相關信息。

直接引用。可以理解爲一個內存地址,或者一個偏移量。比如類方法,類變量的直接引用是指向方法區的指針;而實例方法,實例變量的直接引用則是從實例的頭指針開始算起到這個實例變量位置的偏移量

舉個例子來說,現在調用方法hello(),這個方法的地址是1234567,那麼hello就是符號引用,1234567就是直接引用。

在解析階段,虛擬機會把所有的類名,方法名,字段名這些符號引用替換爲具體的內存地址或偏移量,也就是直接引用。

3)初始化

這個階段主要是對類變量初始化,是執行類構造器的過程。

換句話說,只對static修飾的變量或語句進行初始化。

如果初始化一個類的時候,其父類尚未初始化,則優先初始化其父類。

如果同時包含多個靜態變量和靜態代碼塊,則按照自上而下的順序依次執行。

42、爲什麼會有自定義類加載器?

一方面是由於java代碼很容易被反編譯,如果需要對自己的代碼加密的話,可以對編譯後的代碼進行加密,然後再通過實現自己的自定義類加載器進行解密,最後再加載。

另一方面也有可能從非標準的來源加載代碼,比如從網絡來源,那就需要自己實現一個類加載器,從指定源進行加載。

42、什麼是類加載器?

​Java類加載器是Java運行時環境的一部分,負責動態加載Java類到Java虛擬機的內存空間中。類通常是按需加載,即第一次使用該類時才加載。由於有了類加載器,Java運行時系統不需要知道文件與文件系統。學習類加載器時,掌握Java的委派概念很重要。 

43、類加載器是幹什麼的?

類加載器它是在虛擬機中完成的,負責動態加載Java類到Java虛擬機的內存空間中,在經過 Java 編譯器編譯之後就被轉換成 Java 字節代碼(.class 文件)。類加載器負責讀取 Java 字節代碼,並轉換成 java.lang.Class類的一個實例。

44、如何防止訂單重複提交

         首先,前端js攔截,提交訂單前先判斷提交狀態。只有未提交成功可以提交,提交成功後則禁止提交。後臺,一個訂單有唯一的編號,而且有新建、提交支付中,支付失敗,未支付成功等狀態。根據訂單獲取支付狀態即可,失敗可以繼續提交支付,成功的返回結果即可。

45、如何實現分佈式事務

1)2PC即兩階段提交協議,

是將整個事務流程分爲兩個階段,準備階段(Prepare phase)、提交階段(commit

phase),2是指兩個階段,P是指準備階段,C是指提交階段。需要數據庫支持X/A協議

1. 準備階段(Prepare phase):事務管理器給每個參與者發送Prepare消息,每個數據庫參與者在本地執行事務,並寫本地的Undo/Redo日誌,此時事務沒有提交。(Undo日誌是記錄修改前的數據,用於數據庫回滾,Redo日誌是記錄修改後的數據,用於提交事務後寫入數據文件)

2. 提交階段(commit phase):如果事務管理器收到了參與者的執行失敗或者超時消息時,直接給每個參與者發送回滾(Rollback)消息;否則,發送提交(Commit)消息;參與者根據事務管理器的指令執行提交或者回滾操作,並釋放事務處理過程中使用的鎖資源。注意:必須在最後階段釋放鎖資源。

1.1 XA方案(2PC協議)

2PC的傳統方案是在數據庫層面實現的,如Oracle、MySQL都支持2PC協議,爲了統一標準減少行業內不必要的對接成本,需要制定標準化的處理模型及接口標準,國際開放標準組織Open Group定義了分佈式事務處理模型DTP(Distributed Transaction Processing Reference Model)。主要實現思想是一個應用程序擁有2個數據源,把兩個數據庫的操作合併到一個事務。

1.2 Seata方案

Seata是由阿里中間件團隊發起的開源項目 Fescar,後更名爲Seata,它是一個是開源的分佈式事務框架。傳統2PC的問題在Seata中得到了解決,它通過對本地關係數據庫的分支事務的協調來驅動完成全局事務,是工作在應用層的中間件。主要優點是性能較好,且不長時間佔用連接資源,它以高效並且對業務0侵入的方式解決微服務場景下面臨的分佈式事務問題,它目前提供AT模式(即2PC)及TCC模式的分佈式事務解決方案。

Seata的設計思想如下:

Seata的設計目標其一是對業務無侵入,Seata把一個分佈式事務理解成一個包含了若干分支事務的全局事務。全局事務的職責是協調其下管轄的分支事務達成一致,要麼一起成功提交,要麼一起失敗回滾。,通常分支事務本身就是一個關係數據庫的本地事務。

Seata定義了3個組件來協議分佈式事務的處理過程:

Transaction Coordinator (TC): 事務協調器,它是獨立的中間件,需要獨立部署運行,它維護全局事務的運行狀態,接收TM指令發起全局事務的提交與回滾,負責與RM通信協調各各分支事務的提交或回滾。

Transaction Manager (TM): 事務管理器,TM需要嵌入應用程序中工作,它負責開啓一個全局事務,並最終向TC發起全局提交或全局回滾的指令。

Resource Manager (RM): 控制分支事務,負責分支註冊、狀態彙報,並接收事務協調器TC的指令,驅動分支(本地)事務的提交和回滾。

seata客戶端(RM、TM):spring-cloud-alibaba-seata-2.1.0.RELEASE

seata服務端(TC):seata-server-0.7.1

具體的執行流程如下:

1. 用戶服務的 TM 向 TC 申請開啓一個全局事務,全局事務創建成功並生成一個全局唯一的XID。

2. 用戶服務的 RM 向 TC 註冊 分支事務,該分支事務在用戶服務執行新增用戶邏輯,並將其納入 XID 對應全局

事務的管轄。

3. 用戶服務執行分支事務,向用戶表插入一條記錄。

4. 邏輯執行到遠程調用積分服務時(XID 在微服務調用鏈路的上下文中傳播)。積分服務的RM 向 TC 註冊分支事

務,該分支事務執行增加積分的邏輯,並將其納入 XID 對應全局事務的管轄。

5. 積分服務執行分支事務,向積分記錄表插入一條記錄,執行完畢後,返回用戶服務。

6. 用戶服務分支事務執行完畢。

7. TM 向 TC 發起針對 XID 的全局提交或回滾決議。

8. TC 調度 XID 下管轄的全部分支事務完成提交或回滾請求。

Seata實現2PC與傳統2PC的差別:

架構層次方面,傳統2PC方案的 RM 實際上是在數據庫層,RM 本質上就是數據庫自身,通過 XA 協議實現,而Seata的 RM 是以jar包的形式作爲中間件層部署在應用程序這一側的。

兩階段提交方面,傳統2PC無論第二階段的決議是commit還是rollback,事務性資源的鎖都要保持到Phase2完成才釋放。而Seata的做法是在Phase1 就將本地事務提交,這樣就可以省去Phase2持鎖的時間,整體提高效率。

****小結****

傳統2PC(基於數據庫XA協議)和Seata實現2PC的兩種2PC方案,由於Seata的0侵入性並且解決了傳統2PC長期鎖資源的問題,所以推薦採用Seata實現2PC。

Seata實現2PC要點:

1、全局事務開始使用 @GlobalTransactional標識 。

2、每個本地事務方案仍然使用@Transactional標識。

3、每個數據都需要創建undo_log表,此表是seata保證本地事務一致性的關鍵

 

2)分佈式事務解決方案之TCC

TCC是Try、Confirm、Cancel三個詞語的縮寫,TCC要求每個分支事務實現三個操作:預處理Try、確認Confirm、撤銷Cancel。Try操作做業務檢查及資源預留,Confirm做業務確認操作,Cancel實現一個與Try相反的操作即回滾操作。TM首先發起所有的分支事務的try操作,任何一個分支事務的try操作執行失敗,TM將會發起所有分支事務的Cancel操作,若try操作全部成功,TM將會發起所有分支事務的Confirm操作,其中Confirm/Cancel

操作若執行失敗,TM會進行重試。

TCC分爲三個階段:

1. Try 階段是做業務檢查(一致性)及資源預留(隔離),此階段僅是一個初步操作,它和後續的Confirm 一起才能真正構成一個完整的業務邏輯。

2. Confirm 階段是做確認提交,Try階段所有分支事務執行成功後開始執行 Confirm。通常情況下,採用TCC則認爲 Confirm階段是不會出錯的。即:只要Try成功,Confirm一定成功。若Confirm階段真的出錯了,需引入重試機制或人工處理。

3. Cancel 階段是在業務執行錯誤需要回滾的狀態下執行分支事務的業務取消,預留資源釋放。通常情況下,採用TCC則認爲Cancel階段也是一定成功的。若Cancel階段真的出錯了,需引入重試機制或人工處理。

4. TM事務管理器,TM事務管理器可以實現爲獨立的服務,也可以讓全局事務發起方充當TM的角色,TM獨立出來是爲了成爲公用組件,是爲了考慮系統結構和軟件複用。

TM在發起全局事務時生成全局事務記錄,全局事務ID貫穿整個分佈式事務調用鏈條,用來記錄事務上下文,追蹤和記錄狀態,由於Confirm 和cancel失敗需進行重試,因此需要實現爲冪等,冪等性是指同一個操作無論請求多少次,其結果都相同。

2.1 Hmily

是一個高性能分佈式事務TCC開源框架。基於Java語言來開發(JDK1.8),支持Dubbo,Spring Cloud等RPC框架進行分佈式事務。但Seata的TCC模式對Spring Cloud並沒有提供支持。它目前支持以下特性:

支持嵌套事務(Nested transaction support).

採用disruptor框架進行事務日誌的異步讀寫,與RPC框架的性能毫無差別。

支持SpringBoot-starter 項目啓動,使用簡單。

RPC框架支持 : dubbo,motan,springcloud。

本地事務存儲支持 : redis,mongodb,zookeeper,file,mysql。

事務日誌序列化支持 :java,hessian,kryo,protostuff。

採用Aspect AOP 切面思想與Spring無縫集成,天然支持集羣。

RPC事務恢復,超時異常恢復等。

Hmily利用AOP對參與分佈式事務的本地方法與遠程方法進行攔截處理,通過多方攔截,事務參與者能透明的調用到另一方的Try、Confirm、Cancel方法;傳遞事務上下文;並記錄事務日誌,酌情進行補償,重試等。

Hmily不需要事務協調服務,但需要提供一個數據庫(mysql/mongodb/zookeeper/redis/file)來進行日誌存儲。

Hmily實現的TCC服務與普通的服務一樣,只需要暴露一個接口,也就是它的Try業務。Confirm/Cancel業務邏輯,只是因爲全局事務提交/回滾的需要才提供的,因此Confirm/Cancel業務只需要被Hmily TCC事務框架發現即可,不需要被調用它的其他業務服務所感知。

TCC需要注意三種異常處理分別是空回滾、冪等、懸掛:

空回滾:在沒有調用 TCC 資源 Try 方法的情況下,調用了二階段的 Cancel 方法,Cancel 方法需要識別出這是一個空回滾,然後直接返回成功。

出現原因是當一個分支事務所在服務宕機或網絡異常,分支事務調用記錄爲失敗,這個時候其實是沒有執行Try階段,當故障恢復後,分佈式事務進行回滾則會調用二階段的Cancel方法,從而形成空回滾。

解決思路是關鍵就是要識別出這個空回滾。思路很簡單就是需要知道一階段是否執行,如果執行了,那就是正常回滾;如果沒執行,那就是空回滾。前面已經說過TM在發起全局事務時生成全局事務記錄,全局事務ID貫穿整個分佈式事務調用鏈條。再額外增加一張分支事務記錄表,其中有全局事務 ID 和分支事務 ID,第一階段 Try 方法裏會插入一條記錄,表示一階段執行了。Cancel 接口裏讀取該記錄,如果該記錄存在,則正常回滾;如果該記錄不存在,則是空回滾。

冪等:通過前面介紹已經瞭解到,爲了保證TCC二階段提交重試機制不會引發數據不一致,要求 TCC 的二階段 Try、Confirm 和 Cancel 接口保證冪等,這樣不會重複使用或者釋放資源。如果冪等控制沒有做好,很有可能導致數據不一致等嚴重問題。

解決思路在上述“分支事務記錄”中增加執行狀態,每次執行前都查詢該狀態。

懸掛:懸掛就是對於一個分佈式事務,其二階段 Cancel 接口比 Try 接口先執行。

出現原因是在 RPC 調用分支事務try時,先註冊分支事務,再執行RPC調用,如果此時 RPC 調用的網絡發生擁堵,通常 RPC 調用是有超時時間的,RPC 超時以後,TM就會通知RM回滾該分佈式事務,可能回滾完成後,RPC 請求才到達參與者真正執行,而一個 Try 方法預留的業務資源,只有該分佈式事務才能使用,該分佈式事務第一階段預留的業務資源就再也沒有人能夠處理了,對於這種情況,我們就稱爲懸掛,即業務資源預留後沒法繼續處理。

解決思路是如果二階段執行完成,那一階段就不能再繼續執行。在執行一階段事務時判斷在該全局事務下,“分支事務記錄”表中是否已經有二階段事務記錄,如果有則不執行Try。

拿TCC事務的處理流程與2PC兩階段提交做比較,2PC通常都是在跨庫的DB層面,而TCC則在應用層面的處理,需要通過業務邏輯來實現。這種分佈式事務的實現方式的優勢在於,可以讓應用自己定義數據操作的粒度,使得降低鎖衝突、提高吞吐量成爲可能。而不足之處則在於對應用的侵入性非常強,業務邏輯的每個分支都需要實現try、confirm、cancel三個操作。此外,其實現難度也比較大,需要按照網絡狀態、系統故障等不同的失敗原因實現不同的回滾策略。

46、什麼是強引用、軟引用、弱引用、虛引用有什麼區別?

1) 強引用,特點:我們平常典型編碼Object obj = new Object()中的obj就是強引用。通過關鍵字new創建的對象所關聯的引用就是強引用。當JVM內存空間不足,JVM寧願拋出OutOfMemoryError運行時錯誤(OOM),使程序異常終止,也不會靠隨意回收具有強引用的“存活”對象來解決內存不足的問題。對於一個普通的對象,如果沒有其他的引用關係,只要超過了引用的作用域或者顯式地將相應(強)引用賦值爲 null,就是可以被垃圾收集的了,具體回收時機還是要看垃圾收集策略。

2))軟引用,特點:軟引用通過SoftReference類實現。 軟引用的生命週期比強引用短一些。只有當 JVM 認爲內存不足時,纔會去試圖回收軟引用指向的對象:即JVM 會確保在拋出 OutOfMemoryError 之前,清理軟引用指向的對象。

軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。後續,我們可以調用ReferenceQueue的poll()方法來檢查是否有它所關心的對象被回收。如果隊列爲空,將返回一個null,否則該方法返回隊列中前面的一個Reference對象。

應用場景:軟引用通常用來實現內存敏感的緩存。如果還有空閒內存,就可以暫時保留緩存,當內存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內存。

)弱引用,弱引用通過WeakReference類實現。 弱引用的生命週期比軟引用短。

在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。由於垃圾回收器是一個優先級很低的線程,因此不一定會很快回收弱引用的對象。弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。

應用場景:弱應用同樣可用於內存敏感的緩存。

4) 虛引用:特點:虛引用也叫幻象引用,通過PhantomReference類來實現。無法通過虛引用訪問對象的任何屬性或函數。幻象引用僅僅是提供了一種確保對象被 finalize 以後,做某些事情的機制。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。

ReferenceQueue queue = new ReferenceQueue ();

PhantomReference pr = new PhantomReference (object, queue);

程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。如果程序發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的內存被回收之前採取一些程序行動。

應用場景:可用來跟蹤對象被垃圾回收器回收的活動,當一個虛引用關聯的對象被垃圾收集器回收之前會收到一條系統通知。

47、說一下"=="和equals方法究竟有什麼區別?

== 可以作用於基本數據類型和引用數據類型

equals 只可以作用於引用數據類型

== 作用於基本數據類型比較的是基本數據類型的“值” 作用於引用數據類型比較的是地址

equals 目標對象沒有重寫equals()的方法的時候 比較的是對像的地址 重寫了equals()比較 的是對象的內容

48、 Redis數據淘汰機制

在 redis 中,允許用戶設置最大使用內存大小 server.maxmemory,在內存限定的情況下是很有用的。譬如,在一臺 8G 機子上部署了 4 個 redis 服務點,每一個服務點分配 1.5G 的內存大小,減少內存緊張的情況,由此獲取更爲穩健的服務。

內存大小有限,需要保存有效的數據?

redis 內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略。

Redis提供了以下幾種數據淘汰策略:

1、 volatile-lru:從設置過期的數據集中淘汰最少使用的數據;

2、volatile-ttl:從設置過期的數據集中淘汰即將過期的數據(離過期時間最近);

3、volatile-random:從設置過期的數據集中隨機選取數據淘汰;

4、allkeys-lru:從所有 數據集中選取使用最少的數據;

5、allkeys-random:從所有數據集中任意選取數據淘汰;

6、no-envicition:不進行淘汰;

49、數據庫插入幾百萬數據怎麼實現?

變多次提交爲一次,獲取一次連接,執行多次插入。在代碼中使用循環插入數據,最後關閉連接。

像這樣的批量插入操作能不使用代碼操作就不使用,可以使用存儲過程來實現。

50、 說一下Spring中的兩大核心?

Spring是什麼?

spring是J2EE應用程序框架,是輕量級的IoC和AOP的容器框架(相對於重量級的EJB),主要是針對javaBean的生命週期進行管理的輕量級容器,可以單獨使用,也可以和Struts框架,ibatis框架等組合使用。

1、IOC(Inversion of Control )或DI(Dependency Injection)

       IOC控制權反轉

          原來:我的Service需要調用DAO,Service就需要創建DAO

          Spring:Spring發現你Service依賴於dao,就給你注入.

核心原理:就是配置文件+反射(工廠也可以)+容器(map)

2、AOP:面向切面編程

核心原理:使用動態代理的設計模式在執行方法前後或出現異常做加入相關邏輯。

      我們主要使用AOP來做:

      1、事務處理

      2、權限判斷

      3、日誌

51、說說常見的集合有哪些吧?

答:Map接口和Collection接口是所有集合框架的父接口:

Collection接口的子接口包括:Set接口和List接口

Map接口的實現類主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等

Set接口的實現類主要有:HashSet、TreeSet、LinkedHashSet等

List接口的實現類主要有:ArrayList、LinkedList、Stack以及Vector等

52、HashMap與HashTable的區別?

HashMap沒有考慮同步,是線程不安全的;Hashtable使用了synchronized關鍵字,是線程安全的;

HashMap允許K/V都爲null;後者K/V都不允許爲null;

HashMap繼承自AbstractMap類;而Hashtable繼承自Dictionary類;

53、HashMap的put方法的具體流程?

下面先來分析一下源碼

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

               boolean evict) {

    HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;

    // 1.如果table爲空或者長度爲0,即沒有元素,那麼使用resize()方法擴容

    if ((tab = table) == null || (n = tab.length) == 0)

        n = (tab = resize()).length;

    // 2.計算插入存儲的數組索引i,此處計算方法同 1.7 中的indexFor()方法

    // 如果數組爲空,即不存在Hash衝突,則直接插入數組

    if ((p = tab[i = (n - 1) & hash]) == null)

        tab[i] = newNode(hash, key, value, null);

    // 3.插入時,如果發生Hash衝突,則依次往下判斷

    else {

        HashMap.Node<K,V> e; K k;

        // a.判斷table[i]的元素的key是否與需要插入的key一樣,若相同則直接用新的value覆蓋掉舊的value

        // 判斷原則equals() - 所以需要當key的對象重寫該方法

        if (p.hash == hash &&

                ((k = p.key) == key || (key != null && key.equals(k))))

            e = p;

        // b.繼續判斷:需要插入的數據結構是紅黑樹還是鏈表

        // 如果是紅黑樹,則直接在樹中插入 or 更新鍵值對

        else if (p instanceof HashMap.TreeNode)

            e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

        // 如果是鏈表,則在鏈表中插入 or 更新鍵值對

        else {

            // i .遍歷table[i],判斷key是否已存在:採用equals對比當前遍歷結點的key與需要插入數據的key

            //    如果存在相同的,則直接覆蓋

            // ii.遍歷完畢後任務發現上述情況,則直接在鏈表尾部插入數據

            //    插入完成後判斷鏈表長度是否 > 8:若是,則把鏈表轉換成紅黑樹

            for (int binCount = 0; ; ++binCount) {

                if ((e = p.next) == null) {

                    p.next = newNode(hash, key, value, null);

                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st

                        treeifyBin(tab, hash);

                    break;

                }

                if (e.hash == hash &&

                        ((k = e.key) == key || (key != null && key.equals(k))))

                    break;

                p = e;

            }

        }

        // 對於情況的後續操作:發現key已存在,直接用新value覆蓋舊value&返回舊value

        if (e != null) { // existing mapping for key

            V oldValue = e.value;

            if (!onlyIfAbsent || oldValue == null)

                e.value = value;

            afterNodeAccess(e);

            return oldValue;

        }

    }

    ++modCount;

    // 插入成功後,判斷實際存在的鍵值對數量size > 最大容量

    // 如果大於則進行擴容

    if (++size > threshold)

        resize();

    // 插入成功時會調用的方法(默認實現爲空)

    afterNodeInsertion(evict);

    return null;

}

簡單總結爲:

1、put(key, value)中直接調用了內部的putVal方法,並且先對key進行了hash操作;

2、putVal方法中,先檢查HashMap數據結構中的索引數組表是否位空,如果是的話則進行一次resize操作;

3、以HashMap索引數組表的長度減一與key的hash值進行與運算,得出在數組中的索引,如果索引指定的位置值爲空,則新建一個k-v的新節點;

4、如果不滿足的3的條件,則說明索引指定的數組位置的已經存在內容,這個時候稱之碰撞出現;

5、在上面判斷流程走完之後,計算HashMap全局的modCount值,以便對外部併發的迭代操作提供修改的Fail-fast判斷提供依據,於此同時增加map中的記錄數,並判斷記錄數是否觸及容量擴充的閾值,觸及則進行一輪resize操作;

6、在步驟4中出現碰撞情況時,從步驟7開始展開新一輪邏輯判斷和處理;

7、判斷key索引到的節點(暫且稱作被碰撞節點)的hash、key是否和當前待插入節點(新節點)的一致,如果是一致的話,則先保存記錄下該節點;如果新舊節點的內容不一致時,則再看被碰撞節點是否是樹(TreeNode)類型,如果是樹類型的話,則按照樹的操作去追加新節點內容;如果被碰撞節點不是樹類型,則說明當前發生的碰撞在鏈表中(此時鏈表尚未轉爲紅黑樹),此時進入一輪循環處理邏輯中;

8、循環中,先判斷被碰撞節點的後繼節點是否爲空,爲空則將新節點作爲後繼節點,作爲後繼節點之後並判斷當前鏈表長度是否超過最大允許鏈表長度8,如果大於的話,需要進行一輪是否轉樹的操作;如果在一開始後繼節點不爲空,則先判斷後繼節點是否與新節點相同,相同的話就記錄並跳出循環;如果兩個條件判斷都滿足則繼續循環,直至進入某一個條件判斷然後跳出循環;

9、步驟8中轉樹的操作treeifyBin,如果map的索引表爲空或者當前索引表長度還小於64(最大轉紅黑樹的索引數組表長度),那麼進行resize操作就行了;否則,如果被碰撞節點不爲空,那麼就順着被碰撞節點這條樹往後新增該新節點;

10、最後,回到那個被記住的被碰撞節點,如果它不爲空,默認情況下,新節點的值將會替換被碰撞節點的值,同時返回被碰撞節點的值(V)。

54、HashMap的擴容操作是怎麼實現的?

HashMap通過resize()方法進行擴容或者初始化的操作,下面是對源碼進行的一些簡單分析:

/**

 * 該函數有2中使用情況:1.初始化哈希表;2.當前數組容量過小,需要擴容

 */

final Node<K,V>[] resize() {

    Node<K,V>[] oldTab = table;// 擴容前的數組(當前數組)

    int oldCap = (oldTab == null) ? 0 : oldTab.length;// 擴容前的數組容量(數組長度)

    int oldThr = threshold;// 擴容前數組的閾值

    int newCap, newThr = 0;

 

    if (oldCap > 0) {

        // 針對情況2:若擴容前的數組容量超過最大值,則不再擴容

        if (oldCap >= MAXIMUM_CAPACITY) {

            threshold = Integer.MAX_VALUE;

            return oldTab;

        }

        // 針對情況2:若沒有超過最大值,就擴容爲原來的2倍(左移1位)

        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&

                oldCap >= DEFAULT_INITIAL_CAPACITY)

            newThr = oldThr << 1; // double threshold

    }

 

    // 針對情況1:初始化哈希表(採用指定或者使用默認值的方式)

    else if (oldThr > 0) // initial capacity was placed in threshold

        newCap = oldThr;

    else {               // zero initial threshold signifies using defaults

        newCap = DEFAULT_INITIAL_CAPACITY;

        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

    }

 

    // 計算新的resize上限

    if (newThr == 0) {

        float ft = (float)newCap * loadFactor;

        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?

                (int)ft : Integer.MAX_VALUE);

    }

    threshold = newThr;

    @SuppressWarnings({"rawtypes","unchecked"})

    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];

    table = newTab;

    if (oldTab != null) {

        // 把每一個bucket都移動到新的bucket中去

        for (int j = 0; j < oldCap; ++j) {

            Node<K,V> e;

            if ((e = oldTab[j]) != null) {

                oldTab[j] = null;

                if (e.next == null)

                    newTab[e.hash & (newCap - 1)] = e;

                else if (e instanceof TreeNode)

                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);

                else { // preserve order

                    Node<K,V> loHead = null, loTail = null;

                    Node<K,V> hiHead = null, hiTail = null;

                    Node<K,V> next;

                    do {

                        next = e.next;

                        if ((e.hash & oldCap) == 0) {

                            if (loTail == null)

                                loHead = e;

                            else

                                loTail.next = e;

                            loTail = e;

                        }

                        else {

                            if (hiTail == null)

                                hiHead = e;

                            else

                                hiTail.next = e;

                            hiTail = e;

                        }

                    } while ((e = next) != null);

                    if (loTail != null) {

                        loTail.next = null;

                        newTab[j] = loHead;

                    }

                    if (hiTail != null) {

                        hiTail.next = null;

                        newTab[j + oldCap] = hiHead;

                    }

                }

            }

        }

    }

    return newTab;

}

當HashMap中的元素個數超過數組大小(數組總大小length,不是數組中個數size)*loadFactor時,就會進行數組擴容,loadFactor的默認值爲0.75,這是一個折中的取值。也就是說,默認情況下,數組大小爲16,那麼當HashMap中元素個數超過16*0.75=12(這個值就是代碼中的threshold值,也叫做臨界值)的時候,就把數組的大小擴展爲 2*16=32,即擴大一倍,然後重新計算每個元素在數組中的位置。

0.75這個值成爲負載因子,那麼爲什麼負載因子爲0.75呢?這是通過大量實驗統計得出來的,如果過小,比如0.5,那麼當存放的元素超過一半時就進行擴容,會造成資源的浪費;如果過大,比如1,那麼當元素滿的時候才進行擴容,會使get,put操作的碰撞機率增加。

可以看到HashMap不是無限擴容的,當達到了實現預定的MAXIMUM_CAPACITY,就不再進行擴容。

55、HashMap是怎麼解決哈希衝突的?

我們首先需要知道什麼是哈希衝突,而在瞭解哈希衝突之前我們還要知道什麼是哈希纔行;

什麼是哈希?

Hash,一般翻譯爲“散列”,也有直接音譯爲“哈希”的,這就是把任意長度的輸入通過散列算法,變換成固定長度的輸出,該輸出就是散列值(哈希值);這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,所以不可能從散列值來唯一的確定輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。

所有散列函數都有如下一個基本特性:根據同一散列函數計算出的散列值如果不同,那麼輸入值肯定也不同。但是,根據同一散列函數計算出的散列值如果相同,輸入值不一定相同。

什麼是哈希衝突?

當兩個不同的輸入值,根據同一散列函數計算出相同的散列值的現象,我們就把它叫做碰撞(哈希碰撞)。

HashMap的數據結構

在Java中,保存數據有兩種比較簡單的數據結構:數組和鏈表。數組的特點是:尋址容易,插入和刪除困難;鏈表的特點是:尋址困難,但插入和刪除容易;所以我們將數組和鏈表結合在一起,發揮兩者各自的優勢,使用一種叫做鏈地址法的方式可以解決哈希衝突:

這樣我們就可以將擁有相同哈希值的對象組織成一個鏈表放在hash值所對應的bucket下,但相比於hashCode返回的int類型,我們HashMap初始的容量大小DEFAULT_INITIAL_CAPACITY = 1 << 4(即2的四次方16)要遠小於int類型的範圍,所以我們如果只是單純的用hashCode取餘來獲取對應的bucket這將會大大增加哈希碰撞的概率,並且最壞情況下還會將HashMap變成一個單鏈表,所以我們還需要對hashCode作一定的優化

hash()函數

上面提到的問題,主要是因爲如果使用hashCode取餘,那麼相當於參與運算的只有hashCode的低位,高位是沒有起到任何作用的,所以我們的思路就是讓hashCode取值出的高位也參與運算,進一步降低hash碰撞的概率,使得數據分佈更平均,我們把這樣的操作稱爲擾動

在JDK 1.8中的hash()函數如下:

static final int hash(Object key) {

    int h;

    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 與自己右移16位進行異或運算(高低位異或)

}

這比在JDK 1.7中,更爲簡潔,相比在1.7中的4次位運算,5次異或運算(9次擾動),在1.8中,只進行了1次位運算和1次異或運算(2次擾動);

JDK1.8新增紅黑樹

通過上面的鏈地址法(使用散列表)和擾動函數我們成功讓我們的數據分佈更平均,哈希碰撞減少,但是當我們的HashMap中存在大量數據時,加入我們某個bucket下對應的鏈表有n個元素,那麼遍歷時間複雜度就爲O(n),爲了針對這個問題,JDK1.8在HashMap中新增了紅黑樹的數據結構,進一步使得遍歷複雜度降低至O(logn);

總結

簡單總結一下HashMap是使用了哪些方法來有效解決哈希衝突的:

1. 使用鏈地址法(使用散列表)來鏈接擁有相同hash值的數據;

2. 使用2次擾動函數(hash函數)來降低哈希衝突的概率,使得數據分佈更平均;

3. 引入紅黑樹進一步降低遍歷的時間複雜度,使得遍歷更快;

56、HashMap爲什麼不直接使用hashCode()處理後的哈希值直接作爲table的下標?

hashCode()方法返回的是int整數類型,其範圍爲-(2 ^ 31)~(2 ^ 31 - 1),約有40億個映射空間,而HashMap的容量範圍是在16(初始化默認值)~2 ^ 30,HashMap通常情況下是取不到最大值的,並且設備上也難以提供這麼多的存儲空間,從而導致通過hashCode()計算出的哈希值可能不在數組大小範圍內,進而無法匹配存儲位置;

那怎麼解決呢?

HashMap自己實現了自己的hash()方法,通過兩次擾動使得它自己的哈希值高低位自行進行異或運算,降低哈希碰撞概率也使得數據分佈更平均;

在保證數組長度爲2的冪次方的時候,使用hash()運算之後的值與運算(&)(數組長度 - 1)來獲取數組下標的方式進行存儲,這樣一來是比取餘操作更加有效率,二來也是因爲只有當數組長度爲2的冪次方時,h&(length-1)纔等價於h%length,三來解決了“哈希值與數組大小範圍不匹配”的問題;

57、爲什麼數組長度要保證爲2的冪次方呢?

只有當數組長度爲2的冪次方時,h&(length-1)纔等價於h%length,即實現了key的定位,2的冪次方也可以減少衝突次數,提高HashMap的查詢效率;

如果 length 爲 2 的次冪 則 length-1 轉化爲二進制必定是 11111……的形式,在於 h 的二進制與操作效率會非常的快,而且空間不浪費;如果 length 不是 2 的次冪,比如 length 爲 15,則 length - 1 爲 14,對應的二進制爲 1110,在於 h 與操作,最後一位都爲 0 ,而 0001,0011,0101,1001,1011,0111,1101 這幾個位置永遠都不能存放元素了,空間浪費相當大,更糟的是這種情況中,數組可以使用的位置比數組長度小了很多,這意味着進一步增加了碰撞的機率,減慢了查詢的效率!這樣就會造成空間的浪費。

那爲什麼是兩次擾動呢?

答:這樣就是加大哈希值低位的隨機性,使得分佈更均勻,從而提高對應數組存儲下標位置的隨機性&均勻性,最終減少Hash衝突,兩次就夠了,已經達到了高位低位同時參與運算的目的;

58、HashMap在JDK1.7和JDK1.8中有哪些不同?

不同

JDK 1.7

JDK 1.8

存儲結構

數組 + 鏈表

數組 + 鏈表 + 紅黑樹

初始化方式

單獨函數:inflateTable()

直接集成到了擴容函數resize()

hash值計算方式

擾動處理 = 9次擾動 = 4次位運算 + 5次異或運算

擾動處理 = 2次擾動 = 1次位運算 + 1次異或運算

存放數據的規則

無衝突時,存放數組;衝突時,存放鏈表

無衝突時,存放數組;衝突 & 鏈表長度 < 8:存放單鏈表;衝突 & 鏈表長度 > 8:樹化並存放紅黑樹

插入數據方式

頭插法(先講原位置的數據移到後1位,再插入數據到該位置)

尾插法(直接插入到鏈表尾部/紅黑樹)

擴容後存儲位置的計算方式

全部按照原來方法進行計算(即hashCode ->> 擾動函數 ->> (h&length-1))

按照擴容後的規律計算(即擴容後的位置=原位置 or 原位置 + 舊容量)

59、爲什麼HashMap中String、Integer這樣的包裝類適合作爲K?

StringInteger等包裝類的特性能夠保證Hash值的不可更改性和計算準確性,能夠有效的減少Hash碰撞的機率都是final類型,即不可變性,保證key的不可更改性,不會存在獲取hash值不同的情況。內部已重寫了equals()hashCode()等方法,遵守了HashMap內部的規範(不清楚可以去上面看看putValue的過程),不容易出現Hash值計算錯誤的情況;

面試官:如果我想要讓自己的Object作爲K應該怎麼辦呢?

重寫hashCode()equals()方法

重寫hashCode()是因爲需要計算存儲數據的存儲位置,需要注意不要試圖從散列碼計算中排除掉一個對象的關鍵部分來提高性能,這樣雖然能更快但可能會導致更多的Hash碰撞;

重寫`equals()`方法,需要遵守自反性、對稱性、傳遞性、一致性以及對於任何非null的引用值xx.equals(null)必須返回false的這幾個特性,目的是爲了保證key在哈希表中的唯一性;

60、ConcurrentHashMap和Hashtable的區別?

ConcurrentHashMap 結合了 HashMap 和 HashTable 二者的優勢。HashMap 沒有考慮同步,HashTable 考慮了同步的問題。但是 HashTable 在每次同步執行時都要鎖住整個結構。ConcurrentHashMap 鎖的方式是稍微細粒度的。

面試官:ConcurrentHashMap的具體實現知道嗎?

答:在JDK1.7中,ConcurrentHashMap採用Segment + HashEntry的方式進行實現,結構如下:

1、該類包含兩個靜態內部類 HashEntry 和 Segment ;前者用來封裝映射表的鍵值對,後者用來充當鎖的角色;

2、Segment 是一種可重入的鎖 ReentrantLock,每個 Segment 守護一個HashEntry 數組裏得元素,當對 HashEntry 數組的數據進行修改時,必須首先獲得對應的 Segment 鎖。

在JDK1.8中,放棄了Segment臃腫的設計,取而代之的是採用Node + CAS + Synchronized來保證併發安全進行實現,結構如下:

1、如果相應位置的Node還沒有初始化,則調用CAS插入相應的數據;

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {

    if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))

        break;                   // no lock when adding to empty bin

}

2、如果相應位置的Node不爲空,且當前該節點不處於移動狀態,則對該節點加synchronized鎖,如果該節點的hash不小於0,則遍歷鏈表更新節點或插入新節點;

if (fh >= 0) {

    binCount = 1;

    for (Node<K,V> e = f;; ++binCount) {

        K ek;

        if (e.hash == hash &&

            ((ek = e.key) == key ||

             (ek != null && key.equals(ek)))) {

            oldVal = e.val;

            if (!onlyIfAbsent)

                e.val = value;

            break;

        }

        Node<K,V> pred = e;

        if ((e = e.next) == null) {

            pred.next = new Node<K,V>(hash, key, value, null);

            break;

        }

    }

}

3、如果該節點是TreeBin類型的節點,說明是紅黑樹結構,則通過putTreeVal方法往紅黑樹中插入節點;如果binCount不爲0,說明put操作對數據產生了影響,如果當前鏈表的個數達到8個,則通過treeifyBin方法轉化爲紅黑樹,如果oldVal不爲空,說明是一次更新操作,沒有對元素個數產生影響,則直接返回舊值;

4、如果插入的是一個新節點,則執行addCount()方法嘗試更新元素個數baseCount;

61、Java集合的快速失敗機制 “fail-fast”?

是java集合的一種錯誤檢測機制,當多個線程對集合進行結構上的改變的操作時,有可能會產生 fail-fast 機制。

例如:假設存在兩個線程(線程1、線程2),線程1通過Iterator在遍歷集合A中的元素,在某個時候線程2修改了集合A的結構(是結構上面的修改,而不是簡單的修改集合元素的內容),那麼這個時候程序就會拋出 ConcurrentModificationException 異常,從而產生fail-fast機制。

原因:迭代器在遍歷時直接訪問集合中的內容,並且在遍歷過程中使用一個 modCount 變量。集合在被遍歷期間如果內容發生變化,就會改變modCount的值。每當迭代器使用hashNext()/next()遍歷下一個元素之前,都會檢測modCount變量是否爲expectedmodCount值,是的話就返回遍歷;否則拋出異常,終止遍歷。

解決辦法:

1. 在遍歷過程中,所有涉及到改變modCount值得地方全部加上synchronized。

2. 使用CopyOnWriteArrayList來替換ArrayList

62、ArrayList 和 Vector 的區別?

這兩個類都實現了 List 接口(List 接口繼承了 Collection 接口),他們都是有序集合,即存儲在這兩個集合中的元素位置都是有順序的,相當於一種動態的數組,我們以後可以按位置索引來取出某個元素,並且其中的數據是允許重複的,這是與 HashSet 之類的集合的最大不同處,HashSet 之類的集合不可以按索引號去檢索其中的元素,也不允許有重複的元素。

ArrayList 與 Vector 的區別主要包括兩個方面:

同步性:

Vector 是線程安全的,也就是說它的方法之間是線程同步(加了synchronized 關鍵字)的,而 ArrayList 是線程不安全的,它的方法之間是線程不同步的。如果只有一個線程會訪問到集合,那最好是使用 ArrayList,因爲它不考慮線程安全的問題,所以效率會高一些;如果有多個線程會訪問到集合,那最好是使用 Vector,因爲不需要我們自己再去考慮和編寫線程安全的代碼。

數據增長:

ArrayList 與 Vector 都有一個初始的容量大小,當存儲進它們裏面的元素的個人超過了容量時,就需要增加 ArrayList 和 Vector 的存儲空間,每次要增加存儲空間時,不是隻增加一個存儲單元,而是增加多個存儲單元,每次增加的存儲單元的個數在內存空間利用與程序效率之間要去的一定的平衡。Vector 在數據滿時(加載因子1)增長爲原來的兩倍(擴容增量:原容量的 2 倍),而 ArrayList 在數據量達到容量的一半時(加載因子 0.5)增長爲原容量的 (0.5 倍 + 1) 個空間。

63、ArrayList和LinkedList的區別?

LinkedList 實現了 List 和 Deque 接口,一般稱爲雙向鏈表;ArrayList 實現了 List 接口,動態數組;

LinkedList 在插入和刪除數據時效率更高,ArrayList 在查找某個 index 的數據時效率更高;

LinkedList 比 ArrayList 需要更多的內存;

面試官:Array 和 ArrayList 有什麼區別?什麼時候該應 Array 而不是 ArrayList 呢?

它們的區別是:

Array 可以包含基本類型和對象類型,ArrayList 只能包含對象類型。

Array 大小是固定的,ArrayList 的大小是動態變化的。

ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。

對於基本類型數據,集合使用自動裝箱來減少編碼工作量。但是,當處理固定大小的基本數據類型的時候,這種方式相對比較慢。

64、HashSet是如何保證數據不可重複的?

HashSet的底層其實就是HashMap,只不過我們HashSet是實現了Set接口並且把數據作爲K值,而V值一直使用一個相同的虛值來保存,我們可以看到源碼:

public boolean add(E e) {

    return map.put(e, PRESENT)==null;// 調用HashMap的put方法,PRESENT是一個至始至終都相同的虛值

}

由於HashMap的K值本身就不允許重複,並且在HashMap中如果K/V相同時,會用新的V覆蓋掉舊的V,然後返回舊的V,那麼在HashSet中執行這一句話始終會返回一個false,導致插入失敗,這樣就保證了數據的不可重複性;

65、BlockingQueue是什麼?

Java.util.concurrent.BlockingQueue是一個隊列,在進行檢索或移除一個元素的時候,它會等待隊列變爲非空;當在添加一個元素時,它會等待隊列中的可用空間。BlockingQueue接口是Java集合框架的一部分,主要用於實現生產者-消費者模式。我們不需要擔心等待生產者有可用的空間,或消費者有可用的對象,因爲它都在BlockingQueue的實現類中被處理了。Java提供了集中BlockingQueue的實現,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。

65、Map 的put和get

Map是以鍵值對來存儲對象的,它的底層實際上是數組和鏈表

當使用put方法時,先查找出數組位置是否存在對象,通過key.hashcode對數組長度取餘;存在,則把裏面的鏈表拿出來,判斷鏈表裏面是否存在key值與傳遞過來的key值一樣的對象,存在,則把傳遞過來的value取代鏈表key對應的value,不存在,則直接通過鏈表的add()方法加到鏈表後面;

當使用get方法時,先查找出數組位置是否存在對象,通過key.hashcode對數組長度取餘;如果不存在,則返回爲空,如果存在,則遍歷鏈表,判斷鏈表裏面是否存在key值與傳遞過來的key值一樣的對象,存在,則把key值對應的value取出返回,不存在,則返回爲空;

66、項目 MySQL 的數據量和併發量有多大?

評註:此題爲走向題,你的回答不同,後面問題走向就變了。

關於容量:單錶行數超過 500 萬行或者單表容量超過2GB,此時就要答分庫分表的中間件了!那後面題目的走向就變爲mycat、sharing-jdbc等分庫分表中間件的底層原理了!

關於併發量:如果併發數過1200,此時就要答利用MQ或者redis等中間件,作爲補償措施,而不能直接操作數據庫。那後面的題目走向就是redis、mq的原理了!

介於面試者還是一個應屆生,我斗膽猜測面試者是這麼答的

回答:數據量估計就三四百萬吧,併發量就五六百左右!

67、你說下數據庫的索引實現和非主鍵的二級索引

從數據結構角度:

B-Tree索引,數據結構就是一顆B+樹。

Hash索引,Hash索引比較的是進行Hash運算之後的Hash值,所以它只能用於等值的過濾,不能用於基於範圍的過濾。基本不用!

R-Tree索引,僅支持geometry數據類型,也基本不用!

至於非主鍵的二級索引,這個實際上問的就是非聚簇索引!非聚簇索引本身就是一顆B+樹,其根節點指向聚簇索引的B+樹,具體的請看這篇文章《MySQL(Innodb)索引的原理》

68、項目用的是 SpringBoot ,你能說下 Spring Boot 與 Spring 的區別嗎?

•       Spring Boot可以建立獨立的Spring應用程序;

•       內嵌瞭如Tomcat,Jetty和Undertow這樣的容器,也就是說可以直接跑起來,用不着再做部署工作了。

•       無需再像Spring那樣搞一堆繁瑣的xml文件的配置;

•       可以自動配置Spring;

•       提供了一些現有的功能,如量度工具,表單數據驗證以及一些外部配置這樣的一些第三方功能;

•       提供的POM可以簡化Maven的配置

69、SpringBoot 的自動配置是怎麼做的?

先答爲什麼需要自動配置?

顧名思義,自動配置的意義是利用這種模式代替了配置 XML 繁瑣模式。以前使用 Spring MVC ,需要進行配置組件掃描、調度器、視圖解析器等,使用 Spring Boot 自動配置後,只需要添加 MVC 組件即可自動配置所需要的 Bean。所有自動配置的實現都在 spring-boot-autoconfigure 依賴中,包括 Spring MVC 、Data 和其它框架的自動配置。

接着答spring-boot-autoconfigure 依賴的工作原理?

spring-boot-autoconfigure 依賴的工作原理很簡單,通過 @EnableAutoConfiguration 核心註解初始化,並掃描 ClassPath 目錄中自動配置類對應依賴。比如工程中有木有添加 Thymeleaf 的 Starter 組件依賴。如果有,就按按一定規則獲取默認配置並自動初始化所需要的 Bean。

其實還能再繼續答@EnableAutoConfiguration 註解的工作原理!不過篇幅太長,答到上面那個地步就夠了!

70、MyBatis 定義的接口,怎麼找到實現的?"

回答:一共五步

•       1. Mapper 接口在初始SqlSessionFactory 註冊的。

•       2. Mapper 接口註冊在了名爲 MapperRegistry 類的 HashMap中, key = Mapper class value = 創建當前Mapper的工廠。

•       3. Mapper 註冊之後,可以從SqlSession中get

•       4. SqlSession.getMapper 運用了 JDK動態代理,產生了目標Mapper接口的代理對象。

•       5. 動態代理的 代理類是 MapperProxy ,這裏邊最終完成了增刪改查方法的調用。

71、Java 內存結構

JVM內存結構主要有三大塊:堆內存、方法區和棧。堆內存是JVM中最大的一塊由年輕代和老年代組成,而年輕代內存又被分成三部分,Eden空間、From Survivor空間、To Survivor空間,默認情況下年輕代按照8:1:1的比例來分配;

方法區存儲類信息、常量、靜態變量等數據,是線程共享的區域,爲與Java堆區分,方法區還有一個別名Non-Heap(非堆);棧又分爲java虛擬機棧和本地方法棧主要用於方法的執行。

72、Minor GC 和 Full GC

堆內存是JVM中最大的一塊由年輕代和老年代組成。

那麼,從年輕代空間(包括 Eden 和 Survivor 區域)回收內存被稱爲 Minor GC。

Major GC 是清理老年代。

Full GC 是清理整個堆空間—包括年輕代和老年代。

73、垃圾回收算法

標記-清除算法、標記整理算法、複製算法、分代收集算法

74、垃圾回收器 G1

評註:上面的題目更深入的問法。JVM可以配置不同的回收器。比如Serial, Parallel和CMS幾種垃圾回收器。以Serial Collector(串行回收器)爲例,它在在年輕代是一個使用標記-複製算法的回收器。在老年代使用的是標記-清掃-整理算法。

另外,關於G1回收器可以問的點很多,此題作者沒有描述清楚究竟問的是G1回收器的那個點,就滿回答一下概念吧!

如果是我來問,我就直接給你場景,問你該用哪種回收器了。直接問回收器,那就比較容易了!

常用參數:

-XX:+UseSerialGC:在新生代和老年代使用串行收集器

-XX:+UseParNewGC:在新生代使用並行收集器

//自己查詢吧,太多了!

回答:

G1 GC是Jdk7的新特性之一、Jdk7+版本都可以自主配置G1作爲JVM GC選項。G1 將整個堆劃分爲一個個大小相等的小塊(每一塊稱爲一個region),每一塊的內存是連續的,每個塊也會充當 Eden、Survivor、Old三種角色,但是它們不是固定的,這使得內存使用更加地靈活。如下圖所示

執行垃圾收集時,收集線程在標記階段和應用程序線程併發執行,標記結束後,G1 也就知道哪些區塊基本上是垃圾,存活對象極少,G1 會先從這些區塊下手,因爲從這些區塊能很快釋放得到很大的可用空間,這也是爲什麼 G1 被取名爲 Garbage-First 的原因。

75、Spring RestTemplate 的具體實現

其實RestTemplate和sl4fj這種門面框架很像,本質就是在Http的網絡請求中增加一個馬甲,本身並沒有自己的實現。對此有疑問的,可以看我的另一篇

《架構師必備,帶你弄清混亂的JAVA日誌體系!》

底層可以支持多種httpclient的http訪問,上層爲ClientHttpRequestFactory接口類,底層如下所示:

那麼RestTemplate則封裝了組裝、發送 HTTP消息,以及解析響應的底層細節。

76、描述下網頁一個 Http 請求,到後端的整個請求過程

利用DNS進行域名解析 --> 發起TCP的3次握手 --> 建立TCP連接後發起http請求 --> 服務器響應http請求,瀏覽器得到html代碼 --> 瀏覽器解析html代碼,並請求html代碼中的資源(如js、css、圖片等) --> 瀏覽器對頁面進行渲染呈現給用戶

77、多線程的常用方法和接口類及線程池的機制

常用方法:

start,run,sleep,wait,notify,notifyAll,join,isAlive,currentThread,interrupt

常用接口類:

Runnable、Callable、Future、FutureTask

78、線程池的機制:

在面向對象編程中,創建和銷燬對象是很費時間的,因爲創建一個對象要獲取內存資源或者其它更多資源。所以提高服務程序效率的一個手段就是儘可能減少創建和銷燬對象的次數,所以出現了池化技術!。

簡單的線程池包括如下四個組成部分即可:

•       線程池管理器(ThreadPoolManager):用於創建並管理線程池

•       工作線程(WorkThread): 線程池中線程

•       任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行

•       任務隊列:用於存放沒有處理的任務。提供一種緩衝機制

79、死鎖

死鎖是指兩個或兩個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去,如果系統資源充足,進程的資源請求都能夠得到滿足,死鎖出現的可能性就很低,否則就會因爭奪有限的資源而陷入死鎖。

產生死鎖的原因主要是:

•       (1) 因爲系統資源不足。

•       (2) 進程運行推進的順序不合適。

•       (3) 資源分配不當等。

80、有比較過 Http 和 RPC 嗎?

只要是遠程調用都可以叫RPC,和是不是通過http沒什麼關係。

那麼,調用過程,也就是通信過程之間需要協議,可以是HTTP協議、dubbo協議等、其他協議等。

81、什麼是降級

在開發高併發系統時有三把利器用來保護系統:緩存、降級和限流。

降級的最終目的是保證核心服務可用,即使是有損的。而且有些服務是無法降級的(如加入購物車、結算)。

81、SpringBoot中starter原理

starter中簡單來講就是引入了一些相關依賴和一些初始化的配置。

爲什麼加了@Configuration註解還是要配置META-INF/spring.factories呢?因爲springboot項目默認只會掃描本項目下的帶@Configuration註解的類,如果自定義starter,不在本工程中,是無法加載的,所以要配置META-INF/spring.factories配置文件,這個由@EnableAutoConfiguration幫我們實現。

82、Java中volatile關鍵字

volatile是Java提供的一種輕量級的同步機制。Java 語言包含兩種內在的同步機制:同步塊(或方法)和 volatile 變量,相比於synchronized(synchronized通常稱爲重量級鎖),volatile更輕量級,因爲它不會引起線程上下文的切換和調度。但是volatile 變量的同步性較差(有時它更簡單並且開銷更低),而且其使用也更容易出錯。

(1)保證可見性,不保證原子性

a.當寫一個volatile變量時,JMM會把該線程本地內存中的變量強制刷新到主內存中去;

b.這個寫會操作會導致其他線程中的緩存無效。

1)volatile不適合複合操作

(2)禁止指令重排

83、併發編程的3個基本概念

1)原子性

定義: 即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。

原子性是拒絕多線程操作的,不論是多核還是單核,具有原子性的量,同一時刻只能有一個線程來對它進行操作。簡而言之,在整個操作過程中不會被線程調度器中斷的操作,都可認爲是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。Java中的原子性操作包括:

a. 基本類型的讀取和賦值操作,且賦值必須是數字賦值給變量,變量之間的相互賦值不是原子性操作。

b.所有引用reference的賦值操作

c.java.concurrent.Atomic.* 包中所有類的一切操作

(2)可見性

定義:指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。

在多線程環境下,一個線程對共享變量的操作對其他線程是不可見的。Java提供了volatile來保證可見性,當一個變量被volatile修飾後,表示着線程本地內存無效,當一個線程修改共享變量後他會立即被更新到主內存中,其他線程讀取共享變量時,會直接從主內存中讀取。當然,synchronize和Lock都可以保證可見性。synchronized和Lock能保證同一時刻只有一個線程獲取鎖然後執行同步代碼,並且在釋放鎖之前會將對變量的修改刷新到主存當中。因此可以保證可見性。

(3)有序性

定義:即程序執行的順序按照代碼的先後順序執行。

Java內存模型中的有序性可以總結爲:如果在本線程內觀察,所有操作都是有序的;如果在一個線程中觀察另一個線程,所有操作都是無序的。

84、什麼是XA協議?

XA是X/Open組織爲DTP(分佈式事務處理)制定的標準協議。XA的目的是保證分佈式事務的ACID特性,就像本地事務一樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章