<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
最近看到一個技術技術問題:synchronized鎖問題?
開啟10000個執行緒,每個執行緒給員工表的money欄位【初始值是0】加1,沒有使用悲觀鎖和樂觀鎖,但是在業務層方法上加了synchronized關鍵字,問題是程式碼執行完畢後資料庫中的money 欄位不是10000,而是小於10000 問題出在哪裡?
Service層程式碼:
SQL程式碼(沒有加悲觀/樂觀鎖):
用1000個執行緒跑程式碼:
簡單來說:多執行緒跑一個使用synchronized關鍵字修飾的方法,方法內操作的是資料庫,按正常邏輯應該最終的值是1000,但經過多次測試,結果是低於1000。這是為什麼呢?
既然測試出來的結果是低於1000,那說明這段程式碼不是執行緒安全的。不是執行緒安全的,那問題出現在哪呢?眾所周知,synchronized方法能夠保證所修飾的程式碼塊、方法保證有序性、原子性、可見性。
講道理,以上的程式碼跑起來,問題中Service層的increaseMoney()是有序的、原子的、可見的,所以斷定跟synchronized應該沒關係。
既然Java層面上找不到原因,那分析一下資料庫層面的吧(因為方法內操作的是資料庫)。在increaseMoney()方法前加了@Transcational註解,說明這個方法是帶有事務的。事務能保證同組的SQL要麼同時成功,要麼同時失敗。講道理,如果沒有報錯的話,應該每個執行緒都對money值進行+1。從理論上來說,結果應該是1000的才對。
根據上面的分析,我懷疑是提問者沒測試好(hhhh,逃),於是我也跑去測試了一下,發現是以提問者的方式來使用是真的有問題。
首先貼一下我的測試程式碼:
@RestController public class EmployeeController { @Autowired private EmployeeService employeeService; @RequestMapping("/add") public void addEmployee() { for (int i = 0; i < 1000; i++) { new Thread(() -> employeeService.addEmployee()).start(); } } } @Service public class EmployeeService { @Autowired private EmployeeRepository employeeRepository; @Transactional public synchronized void addEmployee() { // 查出ID為8的記錄,然後每次將年齡增加一 Employee employee = employeeRepository.getOne(8); System.out.println(employee); Integer age = employee.getAge(); employee.setAge(age + 1); employeeRepository.save(employee); } }
簡單地列印了每次拿到的employee值,並且拿到了SQL執行的順序,如下(貼出小部分):
如下(貼出小部分):
從列印的情況我們可以得出:多執行緒情況下並沒有序列執行addEmployee()方法。這就導致對同一個值做重複的修改,所以最終的數值比1000要少。
發現並不是同步執行的,於是我就懷疑synchronized關鍵字和Spring肯定有點衝突。於是根據這兩個關鍵字搜了一下,找到了問題所在。
我們知道Spring事務的底層是Spring AOP,而Spring AOP的底層是動態代理技術。跟大家一起回顧一下動態代理:
public static void main(String[] args) { // 目標物件 Object target ; Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), Main.class, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 但凡帶有@Transcational註解的方法都會被攔截 // 1... 開啟事務 method.invoke(target); // 2... 提交事務 return null; } }); }
實際上Spring做的處理跟以上的思路是一樣的,我們可以看一下TransactionAspectSupport類中invokeWithinTransaction():
呼叫方法前開啟事務,呼叫方法後提交事務
在多執行緒環境下,就可能會出現:方法執行完了(synchronized程式碼塊執行完了),事務還沒提交,別的執行緒可以進入被synchronized修飾的方法,再讀取的時候,讀到的是還沒提交事務的資料,這個資料不是最新的,所以就出現了這個問題。
從上面我們可以發現,問題所在是因為@Transcational註解和synchronized一起使用了,加鎖的範圍沒有包括到整個事務。所以我們可以這樣做:
新建一個名叫SynchronizedService類,讓其去呼叫addEmployee()方法,整個程式碼如下:
@RestController public class EmployeeController { @Autowired private SynchronizedService synchronizedService ; @RequestMapping("/add") public void addEmployee() { for (int i = 0; i < 1000; i++) { new Thread(() -> synchronizedService.synchronizedAddEmployee()).start(); } } } // 新建的Service類 @Service public class SynchronizedService { @Autowired private EmployeeService employeeService ; // 同步 public synchronized void synchronizedAddEmployee() { employeeService.addEmployee(); } } @Service public class EmployeeService { @Autowired private EmployeeRepository employeeRepository; @Transactional public void addEmployee() { // 查出ID為8的記錄,然後每次將年齡增加一 Employee employee = employeeRepository.getOne(8); System.out.println(Thread.currentThread().getName() + employee); Integer age = employee.getAge(); employee.setAge(age + 1); employeeRepository.save(employee); } }
我們將synchronized鎖的範圍包含到整個Spring事務上,這就不會出現執行緒安全的問題了。在測試的時候,我們可以發現1000個執行緒跑起來比之前要慢得多,當然我們的資料是正確的:
拋開上面事務造成的synchronized失效問題,synchronized本身是悲觀鎖,代價偏高,像資料庫資料修改的執行緒安全問題,可以使用樂觀鎖,在表中新增version欄位,每次修改時預期值與資料庫值比較,失敗的話一定次數自旋嘗試修改,修改成功的話version+1。
到此這篇關於Spring事務管理下synchronized鎖失效問題的文章就介紹到這了,更多相關Spring事務synchronized鎖失效內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45