日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

锁、事务和同步

發布時間:2025/3/15 编程问答 14 豆豆
生活随笔 收集整理的這篇文章主要介紹了 锁、事务和同步 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

關于樂觀鎖、悲觀鎖、事務、synchronized,網上介紹的文章很多。但是,在實際使用中,我們經常要遇到需要組合使用這幾種技術的場景。而這方面的文章卻非常少,本文將著重介紹各種組合使用情況下的行為和問題。

并發下讀寫沖突的問題

在開發中我們經常會遇到需要對某個字段做自增操作,比如說你向銀行存入一筆100元的,那么你的總金額就要增加100元。那么程序中就會使用如下代碼

@Entity @Table(name="test") public class Test extends BaseModel{@Id@GeneratedValue(strategy= GenerationType.AUTO)@Column(name = "id",unique = true, nullable = false)private Long id;private Integer count;public Test() {}public Integer getCount() {return count;}public void setCount(Integer count) {this.count = count;} } @Transactionalpublic interface TestRepository extends BaseRepository<Test, Long> { } @RestController @RequestMapping(value = "/test", produces = "application/json") public class TestController {@Autowiredprivate TestRepository repository;public Test updateMoney() {Test test = repository.findOne(1L);test.setMoney(test.getMoney() + 1);repository.save(test);return test;} }

這段代碼并沒有什么問題,事實上在大部分情況下也能正常工作。但是如果遇到高并發情況,就會發生總消費金額少了的情況。這是由于出現了以下的并發沖突情況:
假設當前總消費金額為100元

時間線程1線程2
T1讀取總金額,100元?
T2總金額增加100,200元?
T3?讀取總金額,100元
T4?總金額增加100,200元
T5寫入新的總金額,200元?
T6?寫入新的總金額,200元?

明明執行了兩次自增操作,但是金額只增加了100元。線程1的自增操作丟失了。

使用同步解決讀寫沖突

這個問題有很多解決方法,最簡單的解決方案是在方法上增加一個同步:

public synchronized Test updateMoney() {Test test = repository.findOne(1L);test.setMoney(test.getMoney() + 1);repository.save(test);return test;}

這樣做的缺點也很明顯:在實際代碼中,一個方法可能要進行很多操作,直接對方法進行同步對性能的影響會比較大。我們可以將這個操作單獨拆分成一個獨立的方法,或者單獨對這一段代碼加同步:

public Test updateScore() {synchronized(this){Test test = repository.findOne(1L);test.setMoney(test.getMoney() + 1);repository.save(test);return test;} }

看上去很簡單,不是么? 但是現實永遠是殘酷的。很多時候讀取和寫入并不總在一起。比如讀取用戶信息,之后我們要檢查這個用戶是否可以存取,存錢是否需要支付手續費等。總而言之,同步可以解決這個問題,但是很多時候是以降低代碼性能為代價。

注意:這里說的性能損失是指代碼,因為同步鎖住的是代碼。

既然同步鎖住的是代碼,那么另一個更嚴重的問題是出現了:分布式場景下,多個程序實例同時運行,同步就失效了。那怎么辦呢?

使用數據庫鎖和事務解決讀寫沖突

鎖的概念

首先需要明確一下鎖的概念,本文中涉及到兩個鎖,一個是Java中的鎖。它鎖的是代碼,其作用等同于synchronized。 第二種是鎖數據的鎖,它鎖住的是數據庫里的數據。它并不是數據庫的一種機制,而是一種處理數據方式。當你使用hibernate來實現樂觀或者悲觀鎖時,hibernate會自動創建一個鎖的執行過程的SQL語句(類似于存儲過程)

- SELECT iD, val1, val2 FROM theTable WHERE iD = @theId; - {code that calculates new values} - UPDATE theTable SET val1 = @newVal1, val2 = @newVal2 WHERE iD = @theId AND val1 = @oldVal1 AND val2 = @oldVal2; - {if AffectedRows == 1 } - {go on with your other code} - {else} - {decide what to do since it has gone bad... in your code} - {endif}

所以,本質上樂觀鎖和悲觀鎖依舊是一段代碼,只是它們的目的是保證數據的同步。

樂觀鎖和悲觀鎖的使用

對于分布式環境,鎖代碼是沒有用的。那么我們就必須使用樂觀或者悲觀鎖去鎖住數據庫的數據。這樣不管誰的程序來讀取數據,都能保證數據不被其他程序篡改。

使用樂觀鎖
使用樂觀鎖,需要在數據庫中指定一個字段作為版本控制字段。hibernate中提供了@Version注解用于指定某個或某幾個字段用于版本控制。

我們在數據庫中增加一個version字段

@Entity @Table(name="test") public class Test extends BaseModel{@Id@GeneratedValue(strategy= GenerationType.AUTO)@Column(name = "id",unique = true, nullable = false)private Long id;private Integer money;@Versionprivate Integer version;public Test() {}public Integer getMoney() {return money;}public void setMoney(Integer money) {this.money = money;}public Integer getVersion() {return version;}public void setVersion(Integer version) {this.version = version;} }

并且在controller中增加@Transactional

@RestController @RequestMapping(value = "/test", produces = "application/json") public class TestController {@Autowiredprivate TestRepository repository;@Transactionalpublic Test updateMoney() {Test test = repository.findOne(1L);test.setMoney(test.getMoney() + 1);repository.save(test);return test;} }

這樣樂觀鎖就被激活了,從讀取數據開始到事務結束的代碼都會在樂觀鎖的控制之中。特別要在注意的是,這里必須為方法加上@Transactional事務。否則樂觀鎖不會生效。

關于樂觀鎖的原理和執行過程,網上有很多資料就不再贅述了。當讀寫數據發生沖突時,樂觀鎖檢測到版本沖突,會拋出異常

2016-12-29 14:29:06.806 ERROR 56302 --- [io-8089-exec-11] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is?org.springframework.orm.ObjectOptimisticLockingFailureException: Object of class [com.baojinsuo.springboot.test.Test] with identifier [1]: optimistic locking failed; nested exception is?org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.baojinsuo.springboot.test.Test#1]] with root cause

通過捕獲異常,我們可以讓程序再次嘗試修改數據,或者直接拋出給用戶。樂觀鎖性能較好,因為它并不是真正的鎖住數據,而只是檢測沖突,一旦發生沖突就會告知用戶。如果程序并不經常遇到并發讀寫沖突,可以使用樂觀鎖提高性能。

使用悲觀鎖
如果希望經常發生讀寫沖突,又希望修改提交成功概率更高,那么可以使用悲觀鎖。悲觀鎖是真正的鎖住數據。在釋放之前,是不允許其他程序訪問的。

要使用悲觀鎖,我們需要新增一個方法

@Transactional public interface TestRepository extends BaseRepository<Test, Long> {@Lock(LockModeType.PESSIMISTIC_WRITE)@Query("select cb from Test as cb where cb.id = :id")Test findOneWithLock(@Param("id") Long id); }

使用悲觀鎖,除非發生死鎖,一般情況不會拋出異常。使用上較簡便,但是性能沒有樂觀鎖那么好,特別是在不經常發生讀寫沖突的情況下。

?

?

總結

以上是生活随笔為你收集整理的锁、事务和同步的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。