面試題記錄three

  1. SpringMVC運行原理

如圖所示:

image
流程說明:

(1)客戶端(瀏覽器)發(fā)送請求梧宫,直接請求到DispatcherServlet他去。

(2)DispatcherServlet根據請求信息調用HandlerMapping,解析請求對應的Handler。

(3)解析到對應的Handler后看杭,開始由HandlerAdapter適配器處理耍贾。

(4)HandlerAdapter會根據Handler來調用真正的處理器開處理請求阅爽,并處理相應的業(yè)務邏輯。

(5)處理器處理完業(yè)務后荐开,會返回一個ModelAndView對象付翁,Model是返回的數據對象,View是個邏輯上的View晃听。

(6)ViewResolver會根據邏輯View查找實際的View百侧。

(7)DispaterServlet把返回的Model傳給View砰识。

(8)通過View返回給請求者(瀏覽器)

2.springbean 生命周期


image
Spring框架中,一旦把一個Bean納入Spring IOC容器之中佣渴,這個Bean的生命周期就會交由容器進行管理辫狼,一般擔當管理角色的是BeanFactory或者ApplicationContext,認識一下Bean的生命周期活動,對更好的利用它有很大的幫助:

下面以BeanFactory為例观话,說明一個Bean的生命周期活動

Bean的建立予借, 由BeanFactory讀取Bean定義文件,并生成各個實例
Setter注入频蛔,執(zhí)行Bean的屬性依賴注入
BeanNameAware的setBeanName(), 如果實現該接口灵迫,則執(zhí)行其setBeanName方法
BeanFactoryAware的setBeanFactory(),如果實現該接口晦溪,則執(zhí)行其setBeanFactory方法
BeanPostProcessor的processBeforeInitialization()瀑粥,如果有關聯的processor,則在Bean初始化之前都會執(zhí)行這個實例的processBeforeInitialization()方法
InitializingBean的afterPropertiesSet()三圆,如果實現了該接口狞换,則執(zhí)行其afterPropertiesSet()方法
Bean定義文件中定義init-method
BeanPostProcessors的processAfterInitialization(),如果有關聯的processor舟肉,則在Bean初始化之前都會執(zhí)行這個實例的processAfterInitialization()方法
DisposableBean的destroy()修噪,在容器關閉時,如果Bean類實現了該接口路媚,則執(zhí)行它的destroy()方法
Bean定義文件中定義destroy-method黄琼,在容器關閉時,可以在Bean定義文件中使用“destory-method”定義的方法
如果使用ApplicationContext來維護一個Bean的生命周期整慎,則基本上與上邊的流程相同脏款,只不過在執(zhí)行BeanNameAware的setBeanName()后,若有Bean類實現了org.springframework.context.ApplicationContextAware接口裤园,則執(zhí)行其setApplicationContext()方法撤师,然后再進行BeanPostProcessors的processBeforeInitialization()
實際上,ApplicationContext除了向BeanFactory那樣維護容器外拧揽,還提供了更加豐富的框架功能剃盾,如Bean的消息,事件處理機制等淤袜。

3.樂觀鎖和悲觀鎖及使用場景

悲觀鎖

悲觀鎖(Pessimistic Lock)痒谴,顧名思義,就是很悲觀饮怯,每次去拿數據的時候都認為別人會修改闰歪,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖蓖墅。

悲觀鎖:假定會發(fā)生并發(fā)沖突库倘,屏蔽一切可能違反數據完整性的操作临扮。

Java synchronized 就屬于悲觀鎖的一種實現,每次線程要修改數據時都先獲得鎖教翩,保證同一時刻只有一個線程能操作數據杆勇,其他線程則會被block。

樂觀鎖

樂觀鎖(Optimistic Lock)饱亿,顧名思義蚜退,就是很樂觀,每次去拿數據的時候都認為別人不會修改彪笼,所以不會上鎖钻注,但是在提交更新的時候會判斷一下在此期間別人有沒有去更新這個數據。樂觀鎖適用于讀多寫少的應用場景配猫,這樣可以提高吞吐量幅恋。

樂觀鎖:假設不會發(fā)生并發(fā)沖突,只在提交操作時檢查是否違反數據完整性泵肄。

樂觀鎖一般來說有以下2種方式:

  1. 使用數據版本(Version)記錄機制實現捆交,這是樂觀鎖最常用的一種實現方式。何謂數據版本腐巢?即為數據增加一個版本標識品追,一般是通過為數據庫表增加一個數字類型的 “version” 字段來實現。當讀取數據時冯丙,將version字段的值一同讀出肉瓦,數據每更新一次,對此version值加一银还。當我們提交更新的時候风宁,判斷數據庫表對應記錄的當前版本信息與第一次取出來的version值進行比對洁墙,如果數據庫表當前版本號與第一次取出來的version值相等蛹疯,則予以更新,否則認為是過期數據热监。

  2. 使用時間戳(timestamp)捺弦。樂觀鎖定的第二種實現方式和第一種差不多,同樣是在需要樂觀鎖控制的table中增加一個字段孝扛,名稱無所謂列吼,字段類型使用時間戳(timestamp), 和上面的version類似,也是在更新提交的時候檢查當前數據庫中數據的時間戳和自己更新前取到的時間戳進行對比苦始,如果一致則OK寞钥,否則就是版本沖突。

Java JUC中的atomic包就是樂觀鎖的一種實現陌选,AtomicInteger 通過CAS(Compare And Set)操作實現線程安全的自增理郑。

MySQL****隱式和顯示鎖定

MySQL InnoDB采用的是兩階段鎖定協議(two-phase locking protocol)蹄溉。在事務執(zhí)行過程中,隨時都可以執(zhí)行鎖定您炉,鎖只有在執(zhí)行 COMMIT或者ROLLBACK的時候才會釋放柒爵,并且所有的鎖是在同一時刻被釋放。前面描述的鎖定都是隱式鎖定赚爵,InnoDB會根據事務隔離級別在需要的時候自動加鎖棉胀。

另外,InnoDB也支持通過特定的語句進行顯示鎖定冀膝,這些語句不屬于SQL規(guī)范:

  • SELECT … LOCK IN SHARE MODE

  • SELECT … FOR UPDATE

實戰(zhàn)

接下來唁奢,我們通過一個具體案例來進行分析:考慮電商系統(tǒng)中的下單流程,商品的庫存量是固定的窝剖,如何保證商品數量不超賣驮瞧? 其實需要保證數據一致性:某個人點擊秒殺后系統(tǒng)中查出來的庫存量和實際扣減庫存時庫存量的一致性就可以。

假設枯芬,MySQL數據庫中商品庫存表tb_product_stock 結構定義如下:

CREATE  TABLE  `tb_product_stock`  (

`id`  bigint(20)  NOT  NULL  AUTO_INCREMENT  COMMENT  '自增ID',

`product_id`  bigint(32)  NOT  NULL  COMMENT  '商品ID',

`number`  INT(8)  NOT  NULL  DEFAULT  0  COMMENT  '庫存數量',

`create_time`  DATETIME  NOT  NULL  COMMENT  '創(chuàng)建時間',

`modify_time`  DATETIME  NOT  NULL  COMMENT  '更新時間',

PRIMARY  KEY  (`id`),

UNIQUE  KEY  `index_pid`  (`product_id`)

)  ENGINE=InnoDB  DEFAULT  CHARSET=utf8  COMMENT='商品庫存表';

對應的POJO類:

class  ProductStock  {

private  Long  productId;  *//**商品**id*

private  Integer number;  *//**庫存量*

public  Long  getProductId() {

return  productId;

}

public  void setProductId(Long  productId) {

this.productId = productId;

}

public  Integer getNumber() {

return  number;

}

public  void setNumber(Integer number) {

this.number = number;

}

}

不考慮并發(fā)的情況下论笔,更新庫存代碼如下:

*/***

*** *更新庫存**(**不考慮并發(fā)**)*

***  *@param*  *productId*

*** *@return*

**/*

public  boolean  updateStockRaw(Long productId){

ProductStock product = query("SELECT * FROM tb_product_stock WHERE product_id=#{productId}", productId);

if  (product.getNumber() >  0) {

int  updateCnt = update("UPDATE tb_product_stock SET number=number-1 WHERE product_id=#{productId} AND number=#{number}", productId, product.getNumber());

if(updateCnt >  0){  *//**更新庫存成功*

return  true;

}

}

return  false;

}

多線程并發(fā)情況下,會存在超賣的可能千所。

悲觀鎖

*/***

*** *更新庫存**(**使用悲觀鎖**)*

***  *@param*  *productId*

*** *@return*

**/*

public  boolean  updateStock(Long productId){

*//**先鎖定商品庫存記錄*

ProductStock product = query("SELECT * FROM tb_product_stock WHERE product_id=#{productId} FOR UPDATE", productId);

if  (product.getNumber() >  0) {

int  updateCnt = update("UPDATE tb_product_stock SET number=number-1 WHERE product_id=#{productId}", productId);

if(updateCnt >  0){  *//**更新庫存成功*

return  true;

}

}

return  false;

}

樂觀鎖

*/***

*** *下單減庫存*

***  *@param*  *productId*

*** *@return*

**/*

public  boolean updateStock(Long  productId){

int updateCnt =  0;

while  (updateCnt ==  0) {

ProductStock product = query("SELECT * FROM tb_product_stock WHERE product_id=#{productId}", productId);

if  (product.getNumber() >  0) {

updateCnt = update("UPDATE tb_product_stock SET number=number-1 WHERE product_id=#{productId} AND number=#{number}", productId, product.getNumber());

if(updateCnt >  0){  *//**更新庫存成功*

return  true;

}

}  else  {  *//**賣完啦*

return  false;

}

}

return  false;

}

使用樂觀鎖更新庫存的時候不加鎖狂魔,當提交更新時需要判斷數據是否已經被修改(AND number=#{number}),只有在 number等于上一次查詢到的number時 才提交更新淫痰。

樂觀鎖與悲觀鎖的區(qū)別

樂觀鎖的思路一般是表中增加版本字段最楷,更新時where語句中增加版本的判斷,算是一種CAS(Compare And Swep)操作待错,商品庫存場景中number起到了版本控制(相當于version)的作用( AND number=#{number})籽孙。

悲觀鎖之所以是悲觀,在于他認為本次操作會發(fā)生并發(fā)沖突火俄,所以一開始就對商品加上鎖(SELECT … FOR UPDATE)犯建,然后就可以安心的做判斷和更新,因為這時候不會有別人更新這條商品庫存瓜客。

從中我們也可以知道只要更新數據是依賴讀取的數據作為基礎條件的适瓦,就會有并發(fā)更新問題,需要樂觀鎖或者悲觀鎖取解決谱仪,特別實在計數表現明顯玻熙。又比如在更新數據不依賴查詢的數據的就不會有問題,例如修改用戶的名稱疯攒,多人同時修改嗦随,結果并不依賴于之前的用戶名字,這就不會有并發(fā)更新問題敬尺。

小結

這里我們通過 MySQL 樂觀鎖與悲觀鎖 解決并發(fā)更新庫存的問題枚尼,當然還有其它解決方案肌毅,例如使用 分布式鎖。目前常見分布式鎖實現有兩種:基于Redis和基于Zookeeper姑原,基于這兩種 業(yè)界也有開源的解決方案悬而,例如 Redisson Distributed locks Apache Curator Shared Lock 锭汛,這里就不細說笨奠,網上Google 一下就有很多資料。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末唤殴,一起剝皮案震驚了整個濱河市般婆,隨后出現的幾起案子,更是在濱河造成了極大的恐慌朵逝,老刑警劉巖蔚袍,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異配名,居然都是意外死亡啤咽,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門渠脉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宇整,“玉大人,你說我怎么就攤上這事芋膘×矍啵” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵为朋,是天一觀的道長臂拓。 經常有香客問我,道長习寸,這世上最難降的妖魔是什么胶惰? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮融涣,結果婚禮上童番,老公的妹妹穿的比我還像新娘精钮。我一直安慰自己威鹿,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布轨香。 她就那樣靜靜地躺著忽你,像睡著了一般。 火紅的嫁衣襯著肌膚如雪臂容。 梳的紋絲不亂的頭發(fā)上科雳,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天根蟹,我揣著相機與錄音,去河邊找鬼糟秘。 笑死简逮,一個胖子當著我的面吹牛,可吹牛的內容都是我干的尿赚。 我是一名探鬼主播散庶,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼凌净!你這毒婦竟也來了悲龟?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤冰寻,失蹤者是張志新(化名)和其女友劉穎须教,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體斩芭,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡轻腺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了划乖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片约计。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖迁筛,靈堂內的尸體忽然破棺而出煤蚌,到底是詐尸還是另有隱情,我是刑警寧澤细卧,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布尉桩,位于F島的核電站,受9級特大地震影響贪庙,放射性物質發(fā)生泄漏蜘犁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一止邮、第九天 我趴在偏房一處隱蔽的房頂上張望这橙。 院中可真熱鬧,春花似錦导披、人聲如沸屈扎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鹰晨。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間模蜡,已是汗流浹背漠趁。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留忍疾,地道東北人闯传。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像卤妒,于是被迫代替她去往敵國和親丸边。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345