幼儿饰品瑜伽美体用品微软
投稿投诉
微软创意
爱情通信
用品婚姻
爱好看病
美体软件
影音星座
瑜伽周边
星座办公
饰品塑形
搞笑减肥
幼儿两性
智家潮品

面试官Java多线程怎么做事务控制?一半人答不上来

  项目代码基于:MySql数据,开发框架为:SpringBoot、Mybatis
  开发语言为:Java8前言
  公司业务中遇到一个需求,需要同时修改最多约5万条数据,而且还不支持批量或异步修改操作。于是只能写个for循环操作,但操作耗时太长,只能一步一步寻找其他解决方案。
  具体操作如下:一、循环操作的代码
  先写一个最简单的for循环代码,看看耗时情况怎么样。一条一条依次对50000条数据进行更新操作耗时:2m27s,1m54sTestvoidupdateStudent(){ListStudentallStudentsstudentMapper。getAll();allStudents。forEach(s{更新教师信息Stringteachers。getTeacher();StringnewTeacherTNOnewRandom()。nextInt(100);s。setTeacher(newTeacher);studentMapper。update(s);});}
  循环修改整体耗时约1分54秒,且代码中没有手动事务控制应该是自动事务提交,所以每次操作事务都会提交所以操作比较慢,我们先对代码中添加手动事务控制,看查询效率怎样。
  最新面试题整理:https:www。javastack。cnmst二、使用手动事务的操作代码
  修改后的代码如下:AutowiredprivateDataSourceTransactionManagerdataSourceTransactionManager;AutowiredprivateTransactionDefinitiontransactionDefinition;由于希望更新操作一次性完成,需要手动控制添加事务耗时:24s从测试结果可以看出,添加事务后插入数据的效率有明显的提升TestvoidupdateStudentWithTrans(){ListStudentallStudentsstudentMapper。getAll();TransactionStatustransactionStatusdataSourceTransactionManager。getTransaction(transactionDefinition);try{allStudents。forEach(s{更新教师信息Stringteachers。getTeacher();StringnewTeacherTNOnewRandom()。nextInt(100);s。setTeacher(newTeacher);studentMapper。update(s);});dataSourceTransactionManager。commit(transactionStatus);}catch(Throwablee){dataSourceTransactionManager。rollback(transactionStatus);throwe;}}
  添加手动事务操控制后,整体耗时约24秒,这相对于自动事务提交的代码,快了约5倍,对于大量循环数据库提交操作,添加手动事务可以有效提高操作效率。三、尝试多线程进行数据修改
  添加数据库手动事务后操作效率有明细提高,但还是比较长,接下来尝试多线程提交看是不是能够再快一些。
  先添加一个Service将批量修改操作整合一下,具体代码如下:StudentServiceImpl。javaServicepublicclassStudentServiceImplimplementsStudentService{AutowiredprivateStudentMapperstudentMapper;AutowiredprivateDataSourceTransactionManagerdataSourceTransactionManager;AutowiredprivateTransactionDefinitiontransactionDefinition;OverridepublicvoidupdateStudents(ListStudentstudents,CountDownLatchthreadLatch){TransactionStatustransactionStatusdataSourceTransactionManager。getTransaction(transactionDefinition);System。out。println(子线程:Thread。currentThread()。getName());try{students。forEach(s{更新教师信息Stringteachers。getTeacher();StringnewTeacherTNOnewRandom()。nextInt(100);s。setTeacher(newTeacher);studentMapper。update(s);});dataSourceTransactionManager。commit(transactionStatus);threadLatch。countDown();}catch(Throwablee){e。printStackTrace();dataSourceTransactionManager。rollback(transactionStatus);}}}
  批量测试代码,我们采用了多线程进行提交,修改后测试代码如下:AutowiredprivateDataSourceTransactionManagerdataSourceTransactionManager;AutowiredprivateTransactionDefinitiontransactionDefinition;AutowiredprivateStudentServicestudentService;对用户而言,27s任是一个较长的时间,我们尝试用多线程的方式来经行修改操作看能否加快处理速度预计创建10个线程,每个线程进行5000条数据修改操作耗时统计1线程数:1耗时:25s2线程数:2耗时:14s3线程数:5耗时:15s4线程数:10耗时:15s5线程数:100耗时:15s6线程数:200耗时:15s7线程数:500耗时:17s8线程数:1000耗时:19s8线程数:2000耗时:23s8线程数:5000耗时:29sTestvoidupdateStudentWithThreads(){查询总数据ListStudentallStudentsstudentMapper。getAll();线程数量finalIntegerthreadCount100;每个线程处理的数据量finalIntegerdataPartionLength(allStudents。size()threadCount1)threadCount;创建多线程处理任务ExecutorServicestudentThreadPoolExecutors。newFixedThreadPool(threadCount);CountDownLatchthreadLatchsnewCountDownLatch(threadCount);for(inti0;ithreadCount;i){每个线程处理的数据ListStudentthreadDatasallStudents。stream()。skip(idataPartionLength)。limit(dataPartionLength)。collect(Collectors。toList());studentThreadPool。execute((){studentService。updateStudents(threadDatas,threadLatchs);});}try{倒计时锁设置超时时间30sthreadLatchs。await(30,TimeUnit。SECONDS);}catch(Throwablee){e。printStackTrace();}System。out。println(主线程完成);}
  多线程提交修改时,我们尝试了不同线程数对提交速度的影响,具体可以看下面表格,
  多线程修改50000条数据时不同线程数耗时对比(秒)
  根据表格,我们线程数增大提交速度并非一直增大,在当前情况下约在25个线程数时,提交速度最快(实际线程数还是需要根据服务器配置实际测试)。
  另外,MySQL系列面试题和答案全部整理好了,微信搜索Java技术栈,在后台发送:面试,可以在线阅读。四、基于两个CountDownLatch控制多线程事务提交
  由于多线程提交时,每个线程事务时单独的,无法保证一致性,我们尝试给多线程添加事务控制,来保证每个线程都是在插入数据完成后在提交事务,
  这里我们使用两个CountDownLatch来控制主线程与子线程事务提交,并设置了超时时间为30秒。我们对代码进行了一点修改:OverridepublicvoidupdateStudentsThread(ListStudentstudents,CountDownLatchthreadLatch,CountDownLatchmainLatch,StudentTaskErrortaskStatus){TransactionStatustransactionStatusdataSourceTransactionManager。getTransaction(transactionDefinition);System。out。println(子线程:Thread。currentThread()。getName());try{students。forEach(s{更新教师信息Stringteachers。getTeacher();StringnewTeacherTNOnewRandom()。nextInt(100);s。setTeacher(newTeacher);studentMapper。update(s);});}catch(Throwablee){taskStatus。setIsError();}finally{threadLatch。countDown();切换到主线程执行}try{mainLatch。await();等待主线程执行}catch(Throwablee){taskStatus。setIsError();}判断是否有错误,如有错误就回滚事务if(taskStatus。getIsError()){dataSourceTransactionManager。rollback(transactionStatus);}else{dataSourceTransactionManager。commit(transactionStatus);}}由于每个线程都是单独的事务,需要添加对线程事务的统一控制我们这边使用两个CountDownLatch对子线程的事务进行控制TestvoidupdateStudentWithThreadsAndTrans(){查询总数据ListStudentallStudentsstudentMapper。getAll();线程数量finalIntegerthreadCount4;每个线程处理的数据量finalIntegerdataPartionLength(allStudents。size()threadCount1)threadCount;创建多线程处理任务ExecutorServicestudentThreadPoolExecutors。newFixedThreadPool(threadCount);CountDownLatchthreadLatchsnewCountDownLatch(threadCount);用于计算子线程提交数量CountDownLatchmainLatchnewCountDownLatch(1);用于判断主线程是否提交StudentTaskErrortaskStatusnewStudentTaskError();用于判断子线程任务是否有错误for(inti0;ithreadCount;i){每个线程处理的数据ListStudentthreadDatasallStudents。stream()。skip(idataPartionLength)。limit(dataPartionLength)。collect(Collectors。toList());studentThreadPool。execute((){studentService。updateStudentsThread(threadDatas,threadLatchs,mainLatch,taskStatus);});}try{倒计时锁设置超时时间30sbooleanawaitthreadLatchs。await(30,TimeUnit。SECONDS);if(!await){等待超时,事务回滚taskStatus。setIsError();}}catch(Throwablee){e。printStackTrace();taskStatus。setIsError();}mainLatch。countDown();切换到子线程执行studentThreadPool。shutdown();关闭线程池System。out。println(主线程完成);}
  本想再次测试一下不同线程数对执行效率的影响时,发现当线程数超过10个时,执行时就报错。具体错误内容如下:Exceptioninthreadpool1thread2org。springframework。transaction。CannotCreateTransactionException:CouldnotopenJDBCConnectionfortransaction;nestedexceptionisjava。sql。SQLTransientConnectionException:HikariPool1Connectionisnotavailable,requesttimedoutafter30055ms。atorg。springframework。jdbc。datasource。DataSourceTransactionManager。doBegin(DataSourceTransactionManager。java:309)atorg。springframework。transaction。support。AbstractPlatformTransactionManager。startTransaction(AbstractPlatformTransactionManager。java:400)atorg。springframework。transaction。support。AbstractPlatformTransactionManager。getTransaction(AbstractPlatformTransactionManager。java:373)atcom。example。springbootmybatis。service。Impl。StudentServiceImpl。updateStudentsThread(StudentServiceImpl。java:58)atcom。example。springbootmybatis。StudentTest。lambdaupdateStudentWithThreadsAndTrans3(StudentTest。java:164)atjava。util。concurrent。ThreadPoolExecutor。runWorker(ThreadPoolExecutor。java:1149)atjava。util。concurrent。ThreadPoolExecutorWorker。run(ThreadPoolExecutor。java:624)atjava。lang。Thread。run(Thread。java:748)Causedby:java。sql。SQLTransientConnectionException:HikariPool1Connectionisnotavailable,requesttimedoutafter30055ms。atcom。zaxxer。hikari。pool。HikariPool。createTimeoutException(HikariPool。java:696)atcom。zaxxer。hikari。pool。HikariPool。getConnection(HikariPool。java:197)atcom。zaxxer。hikari。pool。HikariPool。getConnection(HikariPool。java:162)atcom。zaxxer。hikari。HikariDataSource。getConnection(HikariDataSource。java:128)atorg。springframework。jdbc。datasource。DataSourceTransactionManager。doBegin(DataSourceTransactionManager。java:265)。。。7more
  错误的大致意思时,不能为数据库事务打开jdbcConnection,连接在30s的时候超时了。由于前面启动的十个线程需要等待主线程完成后才能提交,所以一直占用连接未释放,造成后面的进程创建连接超时。
  看错误日志中错误的来源是HikariPool,我们来重新配置一下这个连接池的参数,将最大连接数修改为100,具体配置如下:连接池中允许的最小连接数。缺省值:10spring。datasource。hikari。minimumidle10连接池中允许的最大连接数。缺省值:10spring。datasource。hikari。maximumpoolsize100自动提交spring。datasource。hikari。autocommittrue一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟spring。datasource。hikari。idletimeout30000一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒spring。datasource。hikari。maxlifetime1800000等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException,缺省:30秒
  再次执行测试发现没有报错,修改线程数为20又执行了一下,同样执行成功了。另外,关注公众号Java技术栈,在后台回复:面试,可以获取我整理的Java系列面试题和答案,非常齐全。五、基于TransactionStatus集合来控制多线程事务提交
  在同事推荐下我们使用事务集合来进行多线程事务控制,主要代码如下ServicepublicclassStudentsTransactionThread{AutowiredprivateStudentMapperstudentMapper;AutowiredprivateStudentServicestudentService;AutowiredprivatePlatformTransactionManagertransactionManager;ListTransactionStatustransactionStatusesCollections。synchronizedList(newArrayListTransactionStatus());Transactional(propagationPropagation。REQUIRED,rollbackFor{Exception。class})publicvoidupdateStudentWithThreadsAndTrans()throwsInterruptedException{查询总数据ListStudentallStudentsstudentMapper。getAll();线程数量finalIntegerthreadCount2;每个线程处理的数据量finalIntegerdataPartionLength(allStudents。size()threadCount1)threadCount;创建多线程处理任务ExecutorServicestudentThreadPoolExecutors。newFixedThreadPool(threadCount);CountDownLatchthreadLatchsnewCountDownLatch(threadCount);AtomicBooleanisErrornewAtomicBoolean(false);try{for(inti0;ithreadCount;i){每个线程处理的数据ListStudentthreadDatasallStudents。stream()。skip(idataPartionLength)。limit(dataPartionLength)。collect(Collectors。toList());studentThreadPool。execute((){try{try{studentService。updateStudentsTransaction(transactionManager,transactionStatuses,threadDatas);}catch(Throwablee){e。printStackTrace();isError。set(true);}finally{threadLatchs。countDown();}}catch(Exceptione){e。printStackTrace();isError。set(true);}});}倒计时锁设置超时时间30sbooleanawaitthreadLatchs。await(30,TimeUnit。SECONDS);判断是否超时if(!await){isError。set(true);}}catch(Throwablee){e。printStackTrace();isError。set(true);}if(!transactionStatuses。isEmpty()){if(isError。get()){transactionStatuses。forEach(stransactionManager。rollback(s));}else{transactionStatuses。forEach(stransactionManager。commit(s));}}System。out。println(主线程完成);}}OverrideTransactional(propagationPropagation。REQUIRED,rollbackFor{Exception。class})publicvoidupdateStudentsTransaction(PlatformTransactionManagertransactionManager,ListTransactionStatustransactionStatuses,ListStudentstudents){使用这种方式将事务状态都放在同一个事务里面DefaultTransactionDefinitiondefnewDefaultTransactionDefinition();def。setPropagationBehavior(TransactionDefinition。PROPAGATIONREQUIRESNEW);事物隔离级别,开启新事务,这样会比较安全些。TransactionStatusstatustransactionManager。getTransaction(def);获得事务状态transactionStatuses。add(status);students。forEach(s{更新教师信息Stringteachers。getTeacher();StringnewTeacherTNOnewRandom()。nextInt(100);s。setTeacher(newTeacher);studentMapper。update(s);});System。out。println(子线程:Thread。currentThread()。getName());}
  由于这个中方式去前面方式相同,需要等待线程执行完成后才会提交事务,所有任会占用Jdbc连接池,如果线程数量超过连接池最大数量会产生连接超时。所以在使用过程中任要控制线程数量,六、使用union连接多个select实现批量update
  有些情况写不支持,批量update,但支持insert多条数据,这个时候可尝试将需要更新的数据拼接成多条select语句,然后使用union连接起来,再使用update关联这个数据进行update,具体代码演示如下:updatestudent,((select1asid,teacherAasteacher)union(select2asid,teacherAasteacher)union(select3asid,teacherAasteacher)union(select4asid,teacherAasteacher)。。。。moredata。。。)asnewteachersetstudent。teachernewteacher。teacherwherestudent。idnewteacher。id
  这种方式在Mysql数据库没有配置allowMultiQueriestrue也可以实现批量更新。总结对于大批量数据库操作,使用手动事务提交可以很多程度上提高操作效率多线程对数据库进行操作时,并非线程数越多操作时间越快,按上述示例大约在25个线程时操作时间最快。对于多线程阻塞事务提交时,线程数量不能过多。如果能有办法实现批量更新那是最好
  版权声明:本文为博主圣心的原创文章。原文链接:https:blog。csdn。netqq273766764articledetails119972911

每刻美丨春节美甲图鉴壬寅虎年余额不足咯,迎接新年的时候,美甲当然也要更新换代。换上一款高颜值的开运美甲,癸卯兔年继续活出自己想要的样子吧红色美甲迎接新年,精致的美少女们一定要将美……巴蜀十大笑星景雯,争中江表妹失败,成为中江表姐中江表姐景雯1958出生于成都,出生不到一个月,亲生父母就以30元的价格,把她卖给了无儿无女的养父母。1958年普通城镇职工的工资大约就是30元,大概相当于现在的几千元。……小茶叶撬动大产业,世界红茶技艺中心在英德红旗茶厂启动最是一年春好处,英州茶园吐新绿。4月3日,2023第五届英德红茶头采节暨红旗茶厂世界红茶技艺中心启动仪式在英德红旗厂举行。清远市委副秘书长、广州对口帮扶清远指挥部副总指挥徐懿,……减肥一点也不难,用我教你这个方法,想瘦就瘦!我19年怀孕前是100斤,自己感觉不胖不瘦正好,怀孕开始啥都吃不下,体重九十几斤了,怀孕三个月后胃口大开,因为是双胞胎,我吃完饭半小时就又饿了,感觉肚子就像无底洞,为了孩子,我……三星Galaxyzfold5设计曝光,外屏变大,尺寸可能在3三星Galaxyzfold5的设计曝光,这次外屏变大了,看起来竖向设计的折叠屏手机,接下来的思路都是这样?这手机外屏基本上是要在3。3寸到3。4寸之间,现在的手机是1。9……家长们常常为家教辅导孩子发愁,教你四大绝招,学会了,很实用文孙灵君养儿育女,家长们很焦虑。焦虑在于不会教育孩子,没有有效的方法,每天忙碌,没有成效;而激烈竞争的社会背景下,大家又都想孩子们有个好成绩,为他们未来考名校,高品位就业……揭秘chatgpt不为人知的一面,你知道的不过是冰山一角CHATGDP是一款火爆的人工智能聊天软件,它可以与用户进行互动,提供一些有趣的娱乐和信息查询功能。然而,这个软件的黑暗面也许会让你颤抖不已,它可能会把人类带向未知的境地。……没塑料支架,两千出头买到旗舰芯大内存,能带来怎么样的体验前几天,一加带来了新品机型一加Ace2V,并打出了颠覆行业惯例的Slogan。在中端价位,能用上颠覆这一词语,很显然官方对于一加Ace2V的表现非常自信。恰好这两天有幸提前拿到……杉果日报大侠立志传Steam抢先体验GTA6或2024年发售今天是3月8日妇女节,果娘先祝各位妇女节快乐!早上继续为大家带来最新ACG新闻:1。国产武侠RPG《大侠立志传》开启Steam抢先体验2。《宝可梦GO》联动或购买新……每粒点球所需在对手禁区内触球数排名曼联15枪手16利物浦0点直播吧3月4日讯在对阵曼联的赛前新闻发布会上,有记者向克洛普表示,他们已经连续28场英超没有获得任何一粒点球,利物浦主帅表示:很高兴有人注意到这一点,这个数据让我难以理解。……公牛合作中国航天推出航天级超薄墙壁开关插座12月12日,公牛集团宣布与中国航天展开合作,成为民用电工行业首家中国航天事业合作伙伴,双方将共同探索航天科技与家庭安全用电领域科技的互联互通,实现助力航天事业与企业发展的双赢……手机和电脑终于连通了年前,在网上看到华为手机与华为电脑之间可以实现一碰传,传递文件和图片非常方便,由此想到是否可以在第三方电脑上实现这样的功能。在淘宝上找到了华为一碰传NFC标签贴,于是就想买来自……
你的记忆的青春和梦想属于什么样谈及梦想,青春不可不提。即使梦想可以贯穿人的一生,但最耀眼的时候还是在青春那段日子里。耀武扬威,目空一切的年纪里,唯一能征服我们的就是梦想,我们可以为它日夜兼程,可以为它流血流……绿茵场上的青春绿茵场上的青春中国青年报客户端3月23日,四川成都,四川省第三届贡嘎杯青少年体育联赛在四川师范大学开赛,图为武警警官学院队的队员们庆祝点球大战胜利。联赛包括足球、篮……49岁到59岁的中年人,未来五年要留6条出路,活下去很难《一代宗师》当中,打遍天下无敌手的叶问,说过这么一句话:在我40岁之前,未曾见过什么高山,没想到人生最难翻越的,是生活。高山固然难以翻越,但它的高度终究是有限的。也许,我……麒麟芯片再现江湖!华为Mate40系列新款将发布,取消徕卡标华为Mate40系列于2020年10月份发布,至今过去两年多时间,因为搭载麒麟芯片赋予其特殊的情怀价值。为了满足用户需求,华为竭尽全力延长其生命周期,消息称新款Mate40EP……古风演艺非遗市集露营体验2023长安区春约长安系列活动启动视频加载中。。。雅致惊艳的古风表演、韵味厚重的非遗集市、惬意而舒适露营时光3月31日,长安区在长安唐村中国农业公园举办2023春约长安系列活动暨唐村露营季启动仪式。……6000mAh真大电池,荣耀Play7T是否赌对了消费者胃口在手机追求发展大屏的路上,不少消费者一直有种呼声:那就是出一款大容电池的手机,但众多手机厂商忽视了消费者的这种需求,而是选择采用提高快充效率这种曲线救国的方式来解决手机续航问题……米兰达可儿挑战自己大突破!尝试短发造型狂野妩媚,脸充满科技感39岁的米兰达可儿最近的一组新造型还是很让人眼前一亮的,这次的风格和她以往的风格大不同,在发型方面首先进行了突破,差点都没人出。可儿虽然已经不当模特,但是镜头表现力依然完美。……为什么防关联浏览器比VPN的保护效果更好?每次当您上网,特别是使用WiFi时,都会使您的设备及其所有内容面临被间谍软件和黑客入侵数据的风险。其他人将能够阅读您访问过的所有网站,阅读您发送和接收的任何信息,阅读您发布的任……我们的日子第9期王雪花男友杨思宇残疾后,王宪平虚伪面目尽显此文为猎罪影长对《我们的日子》电视剧人物、剧情的解读,每一期涉及不同的人物与剧情,如果喜欢的话,请右上角点关注文接上回是放手让女儿勇敢追爱,还是固步自封维持己念。……WhatsApp个人账户被封的最全解决方案!大鱼致远WhatsApp个人账号,免费快捷,在全球拥有超过20亿的月活跃用户,打开率高达98。目前是外贸人与客户沟通的必备通讯工具,但WhatsApp也有自己的平台规则,一不小心……一爱你一生,一想你一世,你是今生挚爱,是我无悔的相逢有一种缘,明知花落成殇已是定局,偏偏忍不住情思涌动,所想所念都是过去。有一个人,明知千山万水再难相依,偏偏忍不住魂牵梦绕,脑海心中都是你。红尘一世,感恩有你,虽是情……UFC格斗之夜218赛后复盘全分析(副赛部分)北京时间2月5日午时,UFC格斗之夜218暨UFCVegas拉斯维加斯68在美国顶尖会馆隐秘开幕。本期一共十一场比赛,共出现3场KOTKO获胜,4场降服终结。联合主赛上,美国棕……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网