- SpringMVC運行原理
如圖所示:
流程說明:
(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 生命周期
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種方式:
使用數據版本(Version)記錄機制實現捆交,這是樂觀鎖最常用的一種實現方式。何謂數據版本腐巢?即為數據增加一個版本標識品追,一般是通過為數據庫表增加一個數字類型的 “version” 字段來實現。當讀取數據時冯丙,將version字段的值一同讀出肉瓦,數據每更新一次,對此version值加一银还。當我們提交更新的時候风宁,判斷數據庫表對應記錄的當前版本信息與第一次取出來的version值進行比對洁墙,如果數據庫表當前版本號與第一次取出來的version值相等蛹疯,則予以更新,否則認為是過期數據热监。
使用時間戳(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 一下就有很多資料。