记录一下最近工作中出现的两个多线程的问题
1.项目中是存在多线程任务的,实现方式是使用springboot自带的注解来实现的
@Override @Async("analyseThreadPool") public void refreshProgress(TbProgress tbProgress){
// do something
}
正常来说 这个 实现类里的 refreshProgress方法应该是多线程调用的,虽然没实现Future接口,但是应该不影响功能
实际上这里犯了一个问题
本身这个类就是用 @Service修饰的 实现类impl中的,然后我为了实现类的调用更加独立,又嵌套了一层实现类
也就是 Aimpl 中 @Autowired 实例化了 Bimpl,并且调用了Bimpl中的方法
这看上去也没什么,但是不幸的是B中的的一个 SeqIdGenerator 类 是使用@Autowired实例化的,这个类的作用是读取redis中存放一个全局的数值并预支1000个,供全局取,当全部取完了,这个类会再去redis预支1000个。
但是当多线程中,这个值的获取并不能保证是线程安全的。
按照代码规范,在多线程的接口实现类中,所有的类中的全局变量都应该使用 ThreadLocal 来修饰,具体的例如:
private static ThreadLocal<Date> threadLocal = new ThreadLocal<>();
这样可以保证,每个线程不共享这个变量,即这个变量在每个线程中独享一份分身。当然前提是这个类是单例的,才会存在线程安全问题(springboot的@Service就是单例)
所以在这里,使用了@prototype去修饰这个接口的实现类,试图让他变成多例,来阻止SeqIdGenerator 的产生线程安全问题。
很显然,这是奏效的。
但是不幸的,由于前面提到的” 实现类的嵌套关系 “
@prototype注解只被加在了 Bimpl中,而没有加入在 Aimpl中。
导致每次多线程运行这个功能,总会偶发异常。
解决方案是:
MiningMessageService miningMessageService = SpringUtils.getBean(MiningMessageService.class);
使用SpringUtils去获取bean的实例,这样就可以保证多例。
2.多线程任务中,曾经有前置的删除操作(进行独立任务之前,先清空相关数据),
当时因为不是多线程的,所以使用了 spring自带的事务注解 @Transactional
但是’多线程‘和‘事务’,两个概念其实不是兼容的,甚至是有矛盾的!
所以把@Transactional 先注掉,问题解决