在IoC容器中,bean的獲取主要經過BeanFactory和ApplicationContext獲取,這裏ApplicationContext其實是繼承自BeanFactory的,二者的區別在於BeanFactory對bean的初始化主要是延遲初始化的方式,而ApplicationContext對bean的初始化是在容器啓動時即將全部bean初始化完畢。javascript
Spring IOC容器建立一個Bean實例時,能夠爲Bean指定實例的做用域,做用域包括singleton(單例模式)、prototype(原型模式)、request(HTTP請求)、session(會話)、global-session(全局會話)css
單例(singleton):它是默認的選項,在整個應用中,Spring只爲其生成一個Bean的實例。html
原型(prototype):當每次注入,或者經過Spring IoC容器獲取Bean時,Spring都會爲它建立一個新的實例。前端
會話(session):在Web應用中使用,就是在會話過程當中Spring只建立一個實例。java
請求(request):在Web應用中使用的,就是在一次請求中Spring會建立一個實例,可是不一樣的請求會建立不一樣的實例。mysql
全局會話(global-session):全局會話內有效,假如你在編寫一個標準的基於Servlet的web應用,而且定義了一個或多個具備global session做用域的bean,系統會使用標準的HTTP Session做用域,而且不會引發任何錯誤。web
事務特性(4種): 面試
原子性 (atomicity):強調事務的不可分割. redis
一致性 (consistency):事務的執行的先後數據的完整性保持一致. 算法
隔離性 (isolation):一個事務執行的過程當中,不該該受到其餘事務的干擾
持久性(durability) :事務一旦結束,數據就持久到數據庫
髒讀 :髒讀就是指當一個事務正在訪問數據,而且對數據進行了修改,而這種修改尚未提交到數據庫中,這時,另一個事務也訪問這個數據,而後使用了這個數據。
不可重複讀 :是指在一個事務內,屢次讀同一數據。在這個事務尚未結束時,另一個事務也訪問該同一數據。那麼,在第一個事務中的兩 次讀數據之間,因爲第二個事務的修改,那麼第一個事務兩次讀到的的數據多是不同的。這樣就發生了在一個事務內兩次讀到的數據是不同的,所以稱爲是不 可重複讀。
虛幻讀 :是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的所有數據行。 同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那麼,之後就會發生操做第一個事務的用戶發現表中還有沒有修改的數據行,就好象 發生了幻覺同樣。.
DEFAULT 這是一個PlatfromTransactionManager默認的隔離級別,使用數據庫默認的事務隔離級別.
未提交讀(read uncommited) :髒讀,不可重複讀,虛讀都有可能發生
已提交讀 (read commited):避免髒讀。可是不可重複讀和虛讀有可能發生
可重複讀 (repeatable read) :避免髒讀和不可重複讀.可是虛讀有可能發生.
串行化的 (serializable) :避免以上全部讀問題.
Mysql 默認:可重複讀
Oracle 默認:讀已提交
read uncommited:是最低的事務隔離級別,它容許另一個事務能夠看到這個事務未提交的數據。
read commited:保證一個事物提交後才能被另一個事務讀取。另一個事務不能讀取該事物未提交的數據。
repeatable read:這種事務隔離級別能夠防止髒讀,不可重複讀。可是可能會出現幻象讀。它除了保證一個事務不能被另一個事務讀取未提交的數據以外還避免瞭如下狀況產生(不可重複讀)。
serializable:這是花費最高代價但最可靠的事務隔離級別。事務被處理爲順序執行。除了防止髒讀,不可重複讀以外,還避免了幻象讀(避免三種)。
* 保證同一個事務中
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 若是當前事務存在,則嵌套事務執行
@Transactional 註解只能應用到 public 方法纔有效。
若是不生效,加上@Transactional(rollbackFor = Exception.class)
緣由是:當咱們使用@Transaction 時默認爲RuntimeException(也就是運行時異常)異常纔會回滾。
@SpringBootApplication:申明讓spring boot自動給程序進行必要的配置,這個配置等同於:
@Configuration ,@EnableAutoConfiguration 和 @ComponentScan 三個配置。
@ComponentScan:讓spring Boot掃描到Configuration類並把它加入到程序上下文。
@Configuration :等同於spring的XML配置文件;使用Java代碼能夠檢查類型安全。
@EnableAutoConfiguration :自動配置。
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容器中。
數據庫索引,是數據庫管理系統中一個排序的數據結構,以協助快速查詢、更新數據庫表中數據。採起的是空間換時間的概念。
MyISAM引擎和InnoDB引擎使用B+Tree做爲索引結構
1)普通索引,這是最基本的索引,它沒有任何限制,好比上文中爲title字段建立的索引就是一個普通索引,MyIASM中默認的BTREE類型的索引,也是咱們大多數狀況下用到的索引。
2)惟一索引,與普通索引相似,不一樣的就是:索引列的值必須惟一,但容許有空值(注意和主鍵不一樣)。若是是組合索引,則列值的組合必須惟一,建立方法和普通索引相似。
3)全文索引(FULLTEXT),對於較大的數據集,將你的資料輸入一個沒有FULLTEXT索引的表中,而後建立索引,其速度比把資料輸入現有FULLTEXT索引的速度更爲快。不過切記對於大容量的數據表,生成全文索引是一個很是消耗時間很是消耗硬盤空間的作法。
4)單列索引、多列索引,多個單列索引與單個多列索引的查詢效果不一樣,由於執行查詢時,MySQL只能使用一個索引,會從多個索引中選擇一個限制最爲嚴格的索引。
5)組合索引(最左前綴),平時用的SQL查詢語句通常都有比較多的限制條件,因此爲了進一步榨取MySQL的效率,就要考慮創建組合索引
好比字段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)
1)MyISAM 不支持事務,不支持外鍵,優點是訪問速度快,對事務完整性沒有要求,或者以select、insert爲主的能夠使用
2)InnoDB 支持事務,外鍵約束,自增,寫的效率差一些,更佔據空間,支持行級鎖
3)Memory 使用內存中的內容來建立表,訪問速度很是快,使用哈希索引。可是一旦服務關閉,表中的數據就會丟失。
4)Merge 是一組MyISAM表的組合,這些表必須結構徹底相同,merge自己沒有數據。對merge的查詢、更新、刪除實際是對MyISAM的修改。
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,這樣能夠提升多用戶併發操做的性能。
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個月的記錄將之遷移到歷史子表中;
(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對象進行篩選
外層查詢表小於子查詢表,則用exists,外層查詢表大於子查詢表,則用in,若是外層和子查詢表差很少,則愛用哪一個用哪一個
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)卻能夠重複。
持久化就是把內存的數據寫到磁盤中去,防止服務宕機了內存數據丟失。
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
1)首先redis是默認永不過時的,若是要手動設置時間,須要增量設置過時時間,避免redis中的緩存在同一時間失效。若是是系統級別的緣由,好比宕機,採用主從複製。
2)緩存穿透的問題,經過設置分佈式鎖,取得鎖的進程操做數據庫並更新緩存,沒取得鎖的進程發現有鎖就等待。
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)
主要是用於其餘進程,若是沒有發現有鎖就進入睡眠狀態,設置睡眠時間,以及重試次數(循環次數)
jS作一個狀態碼false,當提交成功後狀態碼爲true,提交前先驗證這個狀態碼是否爲false,不然就返回
WXML,WXSS,javascript,json
消息中間件是程序相互通訊的一種方式,消息隊列是消息中間件的一種實現方式。
1)消息沒有收到,使用事務(有這個註解),接收到消息就返回一個狀態,不然重複發送。性能會下降,相似於微信支付寶支付的異步通知。
2)MQ保存消息丟失,這種狀況基本不存在。AMQ是一種文件存儲形式,它具備寫入速度快和容易恢復的特色。消息存儲在一個個文件中,文件的默認大小爲32M,若是一條消息的大小超過了32M,那麼這個值必須設置大一點。當一個存儲文件中的消息已經所有被消費,那麼這個文件將被標識爲可刪除,在下一個清除階段,這個文件被刪除。
若是要消息記錄能夠考慮持久化到數據庫中
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文本序列化
1)服務提供者在啓動時,向服務註冊中心註冊本身提供的服務
2)服務消費者在啓動時,向註冊中心訂閱本身所須要的服務
3)註冊中心返回服務提供者地址列表給服務消費者。若是有變動,服務註冊中心將使用長鏈接推送變動數據給消費者
4)服務消費者,從服務提供者地址列表中,基於軟負載均衡算法,選一臺提供者進行調用。若是調用失敗,再選另外一臺調用
5) 服務消費者和服務提供者,在內存中累計調用次數和調用時間,定時每分鐘發送一次數據到監控中心
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臺不足過半,沒法選舉=沒法提供任何服務。再啓動一個機器恢復服務。因此宕掉的機器不要過半,過半就會致使沒法正常服務
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
雙向鏈表也叫雙鏈表,是鏈表的一種,它的每一個數據結點中都有兩個指針,分別指向直接後繼和直接前驅。因此,從雙向鏈表中的任意一個結點開始,均可以很方便地訪問它的前驅結點和後繼結點。
1)Lock是一個接口,而synchronized是關鍵字。
2)synchronized會自動釋放鎖,而Lock必須手動釋放鎖。
3)Lock可讓等待鎖的線程響應中斷,而synchronized不會,線程會一直等待下去。
4)經過Lock能夠知道線程有沒有拿到鎖,而synchronized不能。
5)Lock能提升多個線程讀操做的效率。
6)synchronized能鎖住類、方法和代碼塊,而Lock是塊範圍內的
爲了能讓 HashMap 存取高效,儘可能較少碰撞,也就是要儘可能把數據分配均勻,每一個鏈表/紅黑樹長度大體相同。這個實現就是把數據存到哪一個鏈表/紅黑樹中的算法。
建立 AuthenticationToken,而後調用 Subject.login 方法進行登陸認證;
Subject 委託給 SecurityManager;
SecurityManager 委託給 Authenticator 接口;
Authenticator 接口調用 Realm 獲取登陸信息。
1、Subject :當前用戶的操做
2、SecurityManager:用於管理全部的Subject
3、Realms:用於進行權限信息的驗證,認證受權都在這裏
在shiro的用戶權限認證過程當中其經過兩個方法來實現:
1、Authentication:是驗證用戶身份的過程。重寫doGetAuthenticationInfo()方法
2、Authorization:是受權訪問控制,用於對用戶進行的操做進行人證受權,證實該用戶是否容許進行當前操做,如訪問某個連接,某個資源文件等,重寫doGetAuthorizationInfo()
3、實現權限is or須要重寫AuthorizationFilter中的isAccessAllowed(),循環遍歷角色的權限數組,只要包含其一就返回true
其餘組件:
除了以上幾個組件外,Shiro還有幾個其餘組件:
1、SessionManager :Shiro爲任何應用提供了一個會話編程範式。
2、CacheManager :對Shiro的其餘組件提供緩存支持。
4、remenberMe:記住我
Hibernate屬於全自動ORM映射工具,使用Hibernate查詢關聯對象或者關聯集合對象時,能夠根據對象關係模型直接獲取,因此它是全自動的。而Mybatis在查詢關聯對象或關聯集合對象時,須要手動編寫sql來完成,因此,稱之爲半自動ORM映射工具。
association 一對一, 一對多 collection,多對多 discrimination
有聯合查詢和嵌套查詢,聯合查詢是幾個表聯合查詢,只查詢一次, 經過在resultMap裏面配置association節點配置一對一的類就能夠完成;
嵌套查詢是先查一個表,根據這個表裏面的結果的 外鍵id,去再另一個表裏面查詢數據,也是經過association配置,但另一個表的查詢經過select屬性配置。
有聯合查詢和嵌套查詢。聯合查詢是幾個表聯合查詢,只查詢一次,經過在resultMap裏面的collection節點配置一對多的類就能夠完成;嵌套查詢是先查一個表,根據這個表裏面的 結果的外鍵id,去再另一個表裏面查詢數據,也是經過配置collection,但另一個表的查詢經過select節點配置。
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,支持延遲加載的原理都是同樣的。
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 掉並從新更新,若是開啓了二級緩存,則只根據配置判斷是否刷新。
一級緩存
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緩存
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接口實現類的啓動),返回上下文對象
類加載的過程主要分爲三個部分:加載;連接;初始化
而連接又能夠細分爲三個小部分:驗證;準備;解析
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修飾的變量或語句進行初始化。
若是初始化一個類的時候,其父類還沒有初始化,則優先初始化其父類。
若是同時包含多個靜態變量和靜態代碼塊,則按照自上而下的順序依次執行。
一方面是因爲java代碼很容易被反編譯,若是須要對本身的代碼加密的話,能夠對編譯後的代碼進行加密,而後再經過實現本身的自定義類加載器進行解密,最後再加載。
另外一方面也有可能從非標準的來源加載代碼,好比從網絡來源,那就須要本身實現一個類加載器,從指定源進行加載。
Java類加載器是Java運行時環境的一部分,負責動態加載Java類到Java虛擬機的內存空間中。類一般是按需加載,即第一次使用該類時才加載。因爲有了類加載器,Java運行時系統不須要知道文件與文件系統。學習類加載器時,掌握Java的委派概念很重要。
類加載器它是在虛擬機中完成的,負責動態加載Java類到Java虛擬機的內存空間中,在通過 Java 編譯器編譯以後就被轉換成 Java 字節代碼(.class 文件)。類加載器負責讀取 Java 字節代碼,並轉換成 java.lang.Class類的一個實例。
首先,前端js攔截,提交訂單前先判斷提交狀態。只有未提交成功能夠提交,提交成功後則禁止提交。後臺,一個訂單有惟一的編號,並且有新建、提交支付中,支付失敗,未支付成功等狀態。根據訂單獲取支付狀態便可,失敗能夠繼續提交支付,成功的返回結果便可。
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三個操做。此外,其實現難度也比較大,須要按照網絡狀態、系統故障等不一樣的失敗緣由實現不一樣的回滾策略。
1) 強引用,特色:咱們日常典型編碼Object obj = new Object()中的obj就是強引用。經過關鍵字new建立的對象所關聯的引用就是強引用。當JVM內存空間不足,JVM寧願拋出OutOfMemoryError運行時錯誤(OOM),使程序異常終止,也不會靠隨意回收具備強引用的「存活」對象來解決內存不足的問題。對於一個普通的對象,若是沒有其餘的引用關係,只要超過了引用的做用域或者顯式地將相應(強)引用賦值爲 null,就是能夠被垃圾收集的了,具體回收時機仍是要看垃圾收集策略。
2))軟引用,特色:軟引用經過SoftReference類實現。 軟引用的生命週期比強引用短一些。只有當 JVM 認爲內存不足時,纔會去試圖回收軟引用指向的對象:即JVM 會確保在拋出 OutOfMemoryError 以前,清理軟引用指向的對象。
軟引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。後續,咱們能夠調用ReferenceQueue的poll()方法來檢查是否有它所關心的對象被回收。若是隊列爲空,將返回一個null,不然該方法返回隊列中前面的一個Reference對象。
應用場景:軟引用一般用來實現內存敏感的緩存。若是還有空閒內存,就能夠暫時保留緩存,當內存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內存。
3 )弱引用,弱引用經過WeakReference類實現。 弱引用的生命週期比軟引用短。
在垃圾回收器線程掃描它所管轄的內存區域的過程當中,一旦發現了具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快回收弱引用的對象。弱引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。
應用場景:弱應用一樣可用於內存敏感的緩存。
4) 虛引用:特色:虛引用也叫幻象引用,經過PhantomReference類來實現。沒法經過虛引用訪問對象的任何屬性或函數。幻象引用僅僅是提供了一種確保對象被 finalize 之後,作某些事情的機制。若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收器回收。虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象的內存以前,把這個虛引用加入到與之關聯的引用隊列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序能夠經過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。若是程序發現某個虛引用已經被加入到引用隊列,那麼就能夠在所引用的對象的內存被回收以前採起一些程序行動。
應用場景:可用來跟蹤對象被垃圾回收器回收的活動,當一個虛引用關聯的對象被垃圾收集器回收以前會收到一條系統通知。
== 能夠做用於基本數據類型和引用數據類型
equals 只能夠做用於引用數據類型
== 做用於基本數據類型比較的是基本數據類型的「值」 做用於引用數據類型比較的是地址
equals 目標對象沒有重寫equals()的方法的時候 比較的是對像的地址 重寫了equals()比較 的是對象的內容
在 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:不進行淘汰;
變屢次提交爲一次,獲取一次鏈接,執行屢次插入。在代碼中使用循環插入數據,最後關閉鏈接。
像這樣的批量插入操做能不使用代碼操做就不使用,能夠使用存儲過程來實現。
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、日誌
答:Map接口和Collection接口是全部集合框架的父接口:
Collection接口的子接口包括:Set接口和List接口
Map接口的實現類主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
Set接口的實現類主要有:HashSet、TreeSet、LinkedHashSet等
List接口的實現類主要有:ArrayList、LinkedList、Stack以及Vector等
HashMap沒有考慮同步,是線程不安全的;Hashtable使用了synchronized關鍵字,是線程安全的;
HashMap容許K/V都爲null;後者K/V都不容許爲null;
HashMap繼承自AbstractMap類;而Hashtable繼承自Dictionary類;
下面先來分析一下源碼
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;
}
}
// 對於i 狀況的後續操做:發現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)。
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,就再也不進行擴容。
咱們首先須要知道什麼是哈希衝突,而在瞭解哈希衝突以前咱們還要知道什麼是哈希才行;
什麼是哈希?
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. 引入紅黑樹進一步下降遍歷的時間複雜度,使得遍歷更快;
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,三來解決了「哈希值與數組大小範圍不匹配」的問題;
只有當數組長度爲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衝突,兩次就夠了,已經達到了高位低位同時參與運算的目的;
不一樣 |
JDK 1.7 |
JDK 1.8 |
存儲結構 |
數組 + 鏈表 |
數組 + 鏈表 + 紅黑樹 |
初始化方式 |
單獨函數: |
直接集成到了擴容函數 |
hash值計算方式 |
擾動處理 = 9次擾動 = 4次位運算 + 5次異或運算 |
擾動處理 = 2次擾動 = 1次位運算 + 1次異或運算 |
存放數據的規則 |
無衝突時,存放數組;衝突時,存放鏈表 |
無衝突時,存放數組;衝突 & 鏈表長度 < 8:存放單鏈表;衝突 & 鏈表長度 > 8:樹化並存放紅黑樹 |
插入數據方式 |
頭插法(先講原位置的數據移到後1位,再插入數據到該位置) |
尾插法(直接插入到鏈表尾部/紅黑樹) |
擴容後存儲位置的計算方式 |
所有按照原來方法進行計算(即hashCode ->> 擾動函數 ->> (h&length-1)) |
按照擴容後的規律計算(即擴容後的位置=原位置 or 原位置 + 舊容量) |
String、Integer等包裝類的特性可以保證Hash值的不可更改性和計算準確性,可以有效的減小Hash碰撞的概率都是final類型,即不可變性,保證key的不可更改性,不會存在獲取hash值不一樣的狀況。內部已重寫了equals()、hashCode()等方法,遵照了HashMap內部的規範(不清楚能夠去上面看看putValue的過程),不容易出現Hash值計算錯誤的狀況;
面試官:若是我想要讓本身的Object做爲K應該怎麼辦呢?
重寫hashCode()和equals()方法
重寫hashCode()是由於須要計算存儲數據的存儲位置,須要注意不要試圖從散列碼計算中排除掉一個對象的關鍵部分來提升性能,這樣雖然能更快但可能會致使更多的Hash碰撞;
重寫`equals()`方法,須要遵照自反性、對稱性、傳遞性、一致性以及對於任何非null的引用值x,x.equals(null)必須返回false的這幾個特性,目的是爲了保證key在哈希表中的惟一性;
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;
是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
這兩個類都實現了 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) 個空間。
LinkedList 實現了 List 和 Deque 接口,通常稱爲雙向鏈表;ArrayList 實現了 List 接口,動態數組;
LinkedList 在插入和刪除數據時效率更高,ArrayList 在查找某個 index 的數據時效率更高;
LinkedList 比 ArrayList 須要更多的內存;
面試官:Array 和 ArrayList 有什麼區別?何時該應 Array 而不是 ArrayList 呢?
它們的區別是:
Array 能夠包含基本類型和對象類型,ArrayList 只能包含對象類型。
Array 大小是固定的,ArrayList 的大小是動態變化的。
ArrayList 提供了更多的方法和特性,好比:addAll(),removeAll(),iterator() 等等。
對於基本類型數據,集合使用自動裝箱來減小編碼工做量。可是,當處理固定大小的基本數據類型的時候,這種方式相對比較慢。
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,致使插入失敗,這樣就保證了數據的不可重複性;
Java.util.concurrent.BlockingQueue是一個隊列,在進行檢索或移除一個元素的時候,它會等待隊列變爲非空;當在添加一個元素時,它會等待隊列中的可用空間。BlockingQueue接口是Java集合框架的一部分,主要用於實現生產者-消費者模式。咱們不須要擔憂等待生產者有可用的空間,或消費者有可用的對象,由於它都在BlockingQueue的實現類中被處理了。Java提供了集中BlockingQueue的實現,好比ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。
Map是以鍵值對來存儲對象的,它的底層其實是數組和鏈表
當使用put方法時,先查找出數組位置是否存在對象,經過key.hashcode對數組長度取餘;存在,則把裏面的鏈表拿出來,判斷鏈表裏面是否存在key值與傳遞過來的key值同樣的對象,存在,則把傳遞過來的value取代鏈表key對應的value,不存在,則直接經過鏈表的add()方法加到鏈表後面;
當使用get方法時,先查找出數組位置是否存在對象,經過key.hashcode對數組長度取餘;若是不存在,則返回爲空,若是存在,則遍歷鏈表,判斷鏈表裏面是否存在key值與傳遞過來的key值同樣的對象,存在,則把key值對應的value取出返回,不存在,則返回爲空;
評註:此題爲走向題,你的回答不一樣,後面問題走向就變了。
關於容量:單錶行數超過 500 萬行或者單表容量超過2GB,此時就要答分庫分表的中間件了!那後面題目的走向就變爲mycat、sharing-jdbc等分庫分表中間件的底層原理了!
關於併發量:若是併發數過1200,此時就要答利用MQ或者redis等中間件,做爲補償措施,而不能直接操做數據庫。那後面的題目走向就是redis、mq的原理了!
介於面試者仍是一個應屆生,我斗膽猜想面試者是這麼答的
回答:數據量估計就三四百萬吧,併發量就五六百左右!
從數據結構角度:
B-Tree索引,數據結構就是一顆B+樹。
Hash索引,Hash索引比較的是進行Hash運算以後的Hash值,因此它只能用於等值的過濾,不能用於基於範圍的過濾。基本不用!
R-Tree索引,僅支持geometry數據類型,也基本不用!
至於非主鍵的二級索引,這個實際上問的就是非聚簇索引!非聚簇索引自己就是一顆B+樹,其根節點指向聚簇索引的B+樹,具體的請看這篇文章《MySQL(Innodb)索引的原理》
• Spring Boot能夠創建獨立的Spring應用程序;
• 內嵌瞭如Tomcat,Jetty和Undertow這樣的容器,也就是說能夠直接跑起來,用不着再作部署工做了。
• 無需再像Spring那樣搞一堆繁瑣的xml文件的配置;
• 能夠自動配置Spring;
• 提供了一些現有的功能,如量度工具,表單數據驗證以及一些外部配置這樣的一些第三方功能;
• 提供的POM能夠簡化Maven的配置
先答爲何須要自動配置?
顧名思義,自動配置的意義是利用這種模式代替了配置 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 註解的工做原理!不過篇幅太長,答到上面那個地步就夠了!
回答:一共五步
• 1. Mapper 接口在初始SqlSessionFactory 註冊的。
• 2. Mapper 接口註冊在了名爲 MapperRegistry 類的 HashMap中, key = Mapper class value = 建立當前Mapper的工廠。
• 3. Mapper 註冊以後,能夠從SqlSession中get
• 4. SqlSession.getMapper 運用了 JDK動態代理,產生了目標Mapper接口的代理對象。
• 5. 動態代理的 代理類是 MapperProxy ,這裏邊最終完成了增刪改查方法的調用。
JVM內存結構主要有三大塊:堆內存、方法區和棧。堆內存是JVM中最大的一塊由年輕代和老年代組成,而年輕代內存又被分紅三部分,Eden空間、From Survivor空間、To Survivor空間,默認狀況下年輕代按照8:1:1的比例來分配;
方法區存儲類信息、常量、靜態變量等數據,是線程共享的區域,爲與Java堆區分,方法區還有一個別名Non-Heap(非堆);棧又分爲java虛擬機棧和本地方法棧主要用於方法的執行。
堆內存是JVM中最大的一塊由年輕代和老年代組成。
那麼,從年輕代空間(包括 Eden 和 Survivor 區域)回收內存被稱爲 Minor GC。
Major GC 是清理老年代。
Full GC 是清理整個堆空間—包括年輕代和老年代。
標記-清除算法、標記整理算法、複製算法、分代收集算法
評註:上面的題目更深刻的問法。JVM能夠配置不一樣的回收器。好比Serial, Parallel和CMS幾種垃圾回收器。以Serial Collector(串行回收器)爲例,它在在年輕代是一個使用標記-複製算法的回收器。在老年代使用的是標記-清掃-整理算法。
另外,關於G1回收器能夠問的點不少,此題做者沒有描述清楚究竟問的是G1回收器的那個點,就滿回答一下概念吧!
若是是我來問,我就直接給你場景,問你該用哪一種回收器了。直接問回收器,那就比較容易了!
經常使用參數:
-XX:+UseSerialGC:在新生代和老年代使用串行收集器
-XX:+UseParNewGC:在新生代使用並行收集器
//本身查詢吧,太多了!
回答:
G1 GC是Jdk7的新特性之1、Jdk7+版本均可以自主配置G1做爲JVM GC選項。G1 將整個堆劃分爲一個個大小相等的小塊(每一塊稱爲一個region),每一塊的內存是連續的,每一個塊也會充當 Eden、Survivor、Old三種角色,可是它們不是固定的,這使得內存使用更加地靈活。以下圖所示
執行垃圾收集時,收集線程在標記階段和應用程序線程併發執行,標記結束後,G1 也就知道哪些區塊基本上是垃圾,存活對象極少,G1 會先從這些區塊下手,由於從這些區塊能很快釋放獲得很大的可用空間,這也是爲何 G1 被取名爲 Garbage-First 的緣由。
其實RestTemplate和sl4fj這種門面框架很像,本質就是在Http的網絡請求中增長一個馬甲,自己並無本身的實現。對此有疑問的,能夠看個人另外一篇
《架構師必備,帶你弄清混亂的JAVA日誌體系!》
底層能夠支持多種httpclient的http訪問,上層爲ClientHttpRequestFactory接口類,底層以下所示:
那麼RestTemplate則封裝了組裝、發送 HTTP消息,以及解析響應的底層細節。
利用DNS進行域名解析 --> 發起TCP的3次握手 --> 創建TCP鏈接後發起http請求 --> 服務器響應http請求,瀏覽器獲得html代碼 --> 瀏覽器解析html代碼,並請求html代碼中的資源(如js、css、圖片等) --> 瀏覽器對頁面進行渲染呈現給用戶
經常使用方法:
start,run,sleep,wait,notify,notifyAll,join,isAlive,currentThread,interrupt
經常使用接口類:
Runnable、Callable、Future、FutureTask
在面向對象編程中,建立和銷燬對象是很費時間的,由於建立一個對象要獲取內存資源或者其它更多資源。因此提升服務程序效率的一個手段就是儘量減小建立和銷燬對象的次數,因此出現了池化技術!。
簡單的線程池包括以下四個組成部分便可:
• 線程池管理器(ThreadPoolManager):用於建立並管理線程池
• 工做線程(WorkThread): 線程池中線程
• 任務接口(Task):每一個任務必須實現的接口,以供工做線程調度任務的執行
• 任務隊列:用於存放沒有處理的任務。提供一種緩衝機制
死鎖是指兩個或兩個以上的進程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去,若是系統資源充足,進程的資源請求都可以獲得知足,死鎖出現的可能性就很低,不然就會因爭奪有限的資源而陷入死鎖。
產生死鎖的緣由主要是:
• (1) 由於系統資源不足。
• (2) 進程運行推動的順序不合適。
• (3) 資源分配不當等。
只要是遠程調用均可以叫RPC,和是否是經過http沒什麼關係。
那麼,調用過程,也就是通訊過程之間須要協議,能夠是HTTP協議、dubbo協議等、其餘協議等。
81、什麼是降級
在開發高併發系統時有三把利器用來保護系統:緩存、降級和限流。
降級的最終目的是保證核心服務可用,即便是有損的。並且有些服務是沒法降級的(如加入購物車、結算)。
starter中簡單來說就是引入了一些相關依賴和一些初始化的配置。
爲何加了@Configuration註解仍是要配置META-INF/spring.factories呢?由於springboot項目默認只會掃描本項目下的帶@Configuration註解的類,若是自定義starter,不在本工程中,是沒法加載的,因此要配置META-INF/spring.factories配置文件,這個由@EnableAutoConfiguration幫咱們實現。
volatile是Java提供的一種輕量級的同步機制。Java 語言包含兩種內在的同步機制:同步塊(或方法)和 volatile 變量,相比於synchronized(synchronized一般稱爲重量級鎖),volatile更輕量級,由於它不會引發線程上下文的切換和調度。可是volatile 變量的同步性較差(有時它更簡單而且開銷更低),並且其使用也更容易出錯。
(1)保證可見性,不保證原子性
a.當寫一個volatile變量時,JMM會把該線程本地內存中的變量強制刷新到主內存中去;
b.這個寫會操做會致使其餘線程中的緩存無效。
1)volatile不適合複合操做
(2)禁止指令重排
1)原子性
定義: 即一個操做或者多個操做 要麼所有執行而且執行的過程不會被任何因素打斷,要麼就都不執行。
原子性是拒絕多線程操做的,不管是多核仍是單核,具備原子性的量,同一時刻只能有一個線程來對它進行操做。簡而言之,在整個操做過程當中不會被線程調度器中斷的操做,均可認爲是原子性。例如 a=1是原子性操做,可是a++和a +=1就不是原子性操做。Java中的原子性操做包括:
a. 基本類型的讀取和賦值操做,且賦值必須是數字賦值給變量,變量之間的相互賦值不是原子性操做。
b.全部引用reference的賦值操做
c.java.concurrent.Atomic.* 包中全部類的一切操做
(2)可見性
定義:指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其餘線程可以當即看獲得修改的值。
在多線程環境下,一個線程對共享變量的操做對其餘線程是不可見的。Java提供了volatile來保證可見性,當一個變量被volatile修飾後,表示着線程本地內存無效,當一個線程修改共享變量後他會當即被更新到主內存中,其餘線程讀取共享變量時,會直接從主內存中讀取。固然,synchronize和Lock均可以保證可見性。synchronized和Lock能保證同一時刻只有一個線程獲取鎖而後執行同步代碼,而且在釋放鎖以前會將對變量的修改刷新到主存當中。所以能夠保證可見性。
(3)有序性
定義:即程序執行的順序按照代碼的前後順序執行。
Java內存模型中的有序性能夠總結爲:若是在本線程內觀察,全部操做都是有序的;若是在一個線程中觀察另外一個線程,全部操做都是無序的。
XA是X/Open組織爲DTP(分佈式事務處理)制定的標準協議。XA的目的是保證分佈式事務的ACID特性,就像本地事務同樣。