欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

鎖超時發(fā)現(xiàn)parallelStream并行流線程上下文坑解決

 更新時間:2023年08月31日 09:22:15   作者:我不是碼農(nóng)  
這篇文章主要為大家介紹了鎖超時發(fā)現(xiàn)parallelStream并行流線程上下文坑解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

detached entity passed to persist問題

就我之前因為在處理jpa持久化對象上下文時,spring jpa關(guān)于線程池異步執(zhí)行導(dǎo)致detached entity passed to persist問題排查和解決

我這邊有個批量插入用戶OpenUser和應(yīng)用OpenApp關(guān)聯(lián)關(guān)系數(shù)據(jù)的操作,由于耗時較長時間,所以準備用線程池異步執(zhí)行操作,然而卻遇到了一個jpa的detached entity passed to persist問題,我這邊的操作是批量保存一個OpenAppUser關(guān)聯(lián)關(guān)系表,所以需要先獲得對應(yīng)OpenUser和OpenApp的引用,再設(shè)置到關(guān)聯(lián)對象OpenAppUser里,然后在保存,我這邊是先通過userRepository.findById(userId)獲取到OpenUser,然后openAppUser.setOpenUser(openUser),在執(zhí)行appUserRepository.save(openAppUser);時發(fā)生了如標題上的錯誤,說是OpenUser對象處于游離態(tài),無法保存。

經(jīng)過排查,我這邊是因為OpenAppUser類里設(shè)置了@ManyToOne(cascade = CascadeType.ALL)級聯(lián)OpenUser,所以在保存OpenAppUser的時候會級聯(lián)操作OpenUser,本來在沒有開線程異步的情況下,因為OpenUser之前通過findById查出來了,所以在jpa的PersistenceContext里是有該OpenUser的脫管對象的,這時候就不會報錯,而在線程異步的情況下context里確沒有該脫管對象了

(這里說明一下,為啥不開線程有,開了線程沒有?)因為spring-boot默認jpa.open-in-view=true,會使用ThreadLocal在當前線程里保存EntityManager上下文信息,所以在整個controller里都是使用的同一個context

PersistenceContext持久性上下文有兩種類型

  • 事務(wù)范圍的持久性上下文;當我們在事務(wù)中執(zhí)行任何操作時,EntityManager 會檢查持久性上下文。 如果存在,則將使用它。否則,它將創(chuàng)建一個持久性上下文
  • 擴展范圍的持久性上下文;擴展持久性上下文可以跨越多個事務(wù)。我們可以在沒有事務(wù)的情況下持久化實體,但不能在沒有事務(wù)的情況下刷新它。

在@PersistenceContext注解里type可以指定范圍:PersistenceContextType.TRANSACTION;PersistenceContextType.EXTENDED

而當我們用線程池異步的時候,拿不到之前的EntityManager的配置信息,而spring jpa repository默認的方法上都會自帶一個事務(wù),所以在執(zhí)行完userRepository.findById(userId)獲取到OpenUser之后,會commit,而commit操作會clear掉EntityManager里保存的脫管對象OpenUser,等到appUserRepository.save(openAppUser);保存的時候,由于引用的OpenUser已經(jīng)沒有在PersistenceContext上下文里了,不是脫管對象了(具體可以看EntityState entityState = getEntityState( entity, entityName, entityEntry, source );里面的實現(xiàn),有幾種判斷條件,是不是脫管對象,有沒有id、version等等屬性),就會報detached entity passed to persist這個異常

所以根據(jù)實際情況,我們只要參考open-in-view=true產(chǎn)生對應(yīng)的OpenEntityManagerInViewInterceptor攔截器改造一下自己線程里的PersistenceContext上下文生效范圍,就可以解決該異常了

parallelStream并行流

parallelStream并行流給我的印象就是會讀不到父線程的上下文的,所以應(yīng)該在父線程里的事務(wù)和在parallelStream里的事務(wù)應(yīng)該是區(qū)分的,而不是共用同一個事務(wù)的,然而今天因為一個鎖超時的問題,發(fā)現(xiàn)并沒有那么簡單,下面我們一步一步來驗證。

鎖超時場景

具體的業(yè)務(wù)我不講了,就說下偽代碼

@PostMapping("/saveUser")
@Transactional
public void saveUser(@RequestBody List<Complex> list) {
    list.parallelStream().forEach(complex->{
        Integer appId = complex.getAppId();
        Integer userId = complex.getUserId();
        GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
        String sql = "insert ignore into open_app_user (app_id, open_id, user_status, creator, modifier, create_time, modify_time, status, version) values ("+appId+","+userId+",0,1,1,now(),now(),1,1)";
        int id = jdbcTemplate.update(con -> con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS), keyHolder);  
    });
    //todo 業(yè)務(wù)邏輯...
}

這里我有個批量保存的邏輯,需要先保存一個中間表open_app_user表(該表app_id和open_id是聯(lián)合唯一鍵)獲得id,拿到用戶的open_app_user_id后再進行其他業(yè)務(wù)邏輯,這里按我原來的理解是雖然我在controller的方法上加了@Transactional注解,但是parallelStream里的事務(wù)應(yīng)該都是獨立的,不會是同一個事務(wù),所以即使有數(shù)據(jù)重復(fù),第一個線程插入后,第二個線程也只會插入失?。ú粫箦e,因為我加了ignore),所以即使并行也不會有問題的,然而卻發(fā)生了鎖超時的問題。

查看鎖超時以及定位的操作可以看我前面的文章,通過查找mysql的 http://www.dbjr.com.cn/article/259480.htm

select * from information_schema.INNODB_TRX;
select * from performance_schema.data_lock_waits;
select * from performance_schema.data_locks;

定位到了這里,然而我也百思不得其解,為啥會鎖超時呢,這里應(yīng)該都是馬上執(zhí)行就馬上釋放了啊,難道是其中的事務(wù)沒有提交?

因為現(xiàn)在都是spring的聲明式事務(wù)管理,spring是在有@Transactional注解的情況下,執(zhí)行完了才提交事務(wù),在沒有@Transactional注解的情況下,每個方法都差不多可以理解成原子,比如我上面的jdbcTemplate.update()這個方法就是一個事務(wù),執(zhí)行完了就直接提交事務(wù)了。

驗證

因為spring是把事務(wù)上下文放在ThreadLocal里了,主要是用TransactionSynchronizationManager這個類來管理,所以我寫了一個demo來進行驗證

@GetMapping("/get")
@Transactional
public String get() {
    List<Complex> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        list.add(new Complex(1, 1));
    }
    list.parallelStream().forEach(complex->{
        Map<Object, Object> resourceMap = TransactionSynchronizationManager.getResourceMap();
        System.err.println("count:"+resourceMap.size());
        Integer appId = complex.getAppId();
        Integer userId = complex.getUserId();
        String sql = "insert ignore into open_app_user (app_id, open_id, user_status, creator, modifier, create_time, modify_time, status, version) values ("+appId+","+userId+",0,1,1,now(),now(),1,1)";
        int update = jdbcTemplate.update(sql);
    });
    return "hello, world! ";
}

有趣的事情發(fā)生了,我在注釋掉@Transactional注解時,代碼里resourceMap.size()返回的內(nèi)容是竟然不一樣,因為我的list有10條記錄,差不多就是10個并行,然而我的輸出卻是:

count:1
count:0
count:0
count:0
count:0
count:0
count:0
count:0
count:0
count:0

沒有注釋掉@Transactional注解時,輸出是:

count:2
count:0
count:0
count:0
count:0
count:0
count:0
count:0
count:0
count:0

并且還會出現(xiàn)鎖超時的現(xiàn)象,奇怪的地方就是為啥我用的parallelStream會有線程上下文里的值,我并沒有做什么操作,而且10個并行里只有一個(這里并不是說明固定只有一次,下面會說明)獲得了線程上下文的信息

測試

我又進一步測試,偽代碼改成:

@GetMapping("/get")
public void get() {
    List<Complex> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        list.add(new Complex(1, 1));
    }
    ThreadLocal local = new ThreadLocal();
    local.set("parent_set_value");
    list.parallelStream().forEach(complex->{
        System.err.println(local.get());
    });
}

結(jié)果如我所料,輸出為:

parent_set_value
null
null
null
null
null
null
null
null
null

使用parallelStream并不完全都是另開了線程,其中有一個是屬于主線程的,可以使用System.err.println(Thread.currentThread().getName());查看當前線程的名稱,我發(fā)現(xiàn)parallelStream會把當前主線程也作為一個執(zhí)行線程去執(zhí)行任務(wù)

后面我再去了解了一下parallelStream的實現(xiàn),在這個方法上的注解里第一句話有個單詞是possibly,是“可能”返回并行流,原來參與并行處理的線程有主線程以及ForkJoinPool中的worker線程,所以parallelStream是有兩種情況的,一是可能只一個線程并發(fā)執(zhí)行,二是多個線程并行執(zhí)行,而我這里導(dǎo)致鎖超時,就是因為用到了主線程,所以在并行插入的時候,有個處理有事務(wù)上下文,導(dǎo)致一直沒有提交事務(wù)(@Transactional注釋方法的方法沒有跑完,這里也不可能跑完),所以其他線程的插入就一直等待這個,產(chǎn)生了鎖超時報錯

以上就是鎖超時發(fā)現(xiàn)parallelStream并行流線程上下文坑解決的詳細內(nèi)容,更多關(guān)于parallelStream并行流線程坑的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SQL返回Map集合或者對象的操作

    SQL返回Map集合或者對象的操作

    這篇文章主要介紹了SQL返回Map集合或者對象的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Spring @Conditional注解原理解析

    Spring @Conditional注解原理解析

    這篇文章主要介紹了Spring @Conditional注解原理解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-11-11
  • springboot 接收List 入?yún)⒌膸追N方法

    springboot 接收List 入?yún)⒌膸追N方法

    本文主要介紹了springboot 接收List 入?yún)⒌膸追N方法,本文主要介紹了7種方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-03-03
  • Java 并發(fā)編程學(xué)習(xí)筆記之核心理論基礎(chǔ)

    Java 并發(fā)編程學(xué)習(xí)筆記之核心理論基礎(chǔ)

    編寫優(yōu)質(zhì)的并發(fā)代碼是一件難度極高的事情。Java語言從第一版本開始內(nèi)置了對多線程的支持,這一點在當年是非常了不起的,但是當我們對并發(fā)編程有了更深刻的認識和更多的實踐后,實現(xiàn)并發(fā)編程就有了更多的方案和更好的選擇。本文是對并發(fā)編程的核心理論做了下小結(jié)
    2016-05-05
  • Java中數(shù)組的創(chuàng)建與傳參方法(學(xué)習(xí)小結(jié))

    Java中數(shù)組的創(chuàng)建與傳參方法(學(xué)習(xí)小結(jié))

    這篇文章主要介紹了Java中數(shù)組的創(chuàng)建與傳參方法,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-09-09
  • Spring?IOC?xml方式進行工廠Bean操作詳解

    Spring?IOC?xml方式進行工廠Bean操作詳解

    這篇文章主要介紹了Spring?IOC?xml方式進行工廠Bean操作,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2023-01-01
  • SpringBoot Controller Post接口單元測試示例

    SpringBoot Controller Post接口單元測試示例

    今天小編就為大家分享一篇關(guān)于SpringBoot Controller Post接口單元測試示例,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • mybatis動態(tài)生成sql語句的實現(xiàn)示例

    mybatis動態(tài)生成sql語句的實現(xiàn)示例

    在MyBatis中,動態(tài)SQL是一個非常重要的特性,它允許我們根據(jù)條件動態(tài)地生成SQL語句,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-11-11
  • Spring覆蓋容器中Bean的注解如何實現(xiàn)@OverrideBean

    Spring覆蓋容器中Bean的注解如何實現(xiàn)@OverrideBean

    文章介紹了在項目開發(fā)中如何通過偷梁換柱的方式重寫Spring容器中的內(nèi)置Bean,并指出了需要注意的兩點:1. 對應(yīng)的Bean應(yīng)基于接口注入;2. 如果不是基于接口注入,可以使用同包名同類名的方式重寫(可能存在潛在問題,不推薦),文章還強調(diào)了“基于接口編程”的好處
    2025-01-01
  • mybatis執(zhí)行批量更新batch update 的方法(oracle,mysql兩種)

    mybatis執(zhí)行批量更新batch update 的方法(oracle,mysql兩種)

    這篇文章主要介紹了mybatis執(zhí)行批量更新batch update 的方法,提供oracle和mysql兩種方法,非常不錯,需要的朋友參考下
    2017-01-01

最新評論