关于线程池与事务的注意问题

1 多线程操作数据库的问题(事务配置采用spring默认策略)

主线程中开启一个子线程,如果子线程出现异常的话,子线程会回滚吗?主线程会回滚吗?

案例:

UserController:

@RequestMapping(value = "add",method= RequestMethod.POST)
public void add(Integer id,String name,String password,int age,int deleteFlag){
    User user = new User();
    user.setId(id);
    user.setName(name);
    user.setPassword(password);
    user.setAge(age);
    user.setDeleteFlag(deleteFlag);
    userService.add(user);

}

UserService:

@Service
@Transactional
public class UserService implements IUserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private AccountService accountService;

    private Executor executor = ThreadPool.getPoll();

    @Override
    public Integer add(User user){
        int result = userMapper.add(user);

        Account account = new Account();
        account.setId(user.getId());
        
        executor.execute(() -> {
            accountService.add(account);
            if (user.getId().intValue() == 11) {
                throw new RuntimeException("执行异常");
            }
        });
        return result;
    }
}

结果:

Exception in thread "ThreadPoolTaskExecutor-1" java.lang.RuntimeException: 执行异常
	at test.service.UserService.lambda$add$0(UserService.java:68)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

主线程的添加user和子线程的添加account都不会进行回滚。

2 解决

2.1 spring的事务管理

spirng的事务管理已经分析过,详细之前的文章,这里只列出一部分关键的结构信息。

TransactionSynchronizationManager:

public abstract class TransactionSynchronizationManager {
	// 线程私有事务资源
	private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");

	// 事务同步
	private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
			new NamedThreadLocal<>("Transaction synchronizations");

	// 当前事务的名称
	private static final ThreadLocal<String> currentTransactionName =
			new NamedThreadLocal<>("Current transaction name");

	// 当前事务是否只读
	private static final ThreadLocal<Boolean> currentTransactionReadOnly =
			new NamedThreadLocal<>("Current transaction read-only status");

	// 当前事务的隔离级别
	private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
			new NamedThreadLocal<>("Current transaction isolation level");

	// 实际事务是否激活
	private static final ThreadLocal<Boolean> actualTransactionActive =
			new NamedThreadLocal<>("Actual transaction active");
}
  • 重点是resources这个变量,首先这是一个threadlocal的结果,枚举中的Map key类型为连接数据库的数据源dataSource,value类型为ConnectionHolder(可以理解为dataSource的一个connect连接)。
  • 当方法执行到spring的doBegin开启事务方法,会先从resources获取,以当前数据源dataSource为key,获取value也就是connect存不存在,如果不存在则从dataSource获取一个connect设置进去,如果存在则直接以当前的connect来使用。
  • 所以在spring事务管理的情况下,父子线程的数据源连接connect是不同的。

2.2 解决思路

虽然父子线程的数据源连接connect不同,但是我们可以通过线程池的特点来解决上述问题。

2.2.1 首先解决子线程出现异常,让父线程回滚。

之前分析了线程池在执行submit时,会将异常放入futureTask中,可以利用这个特点来解决。

案例:

UserService:

public Integer addV2(User user){
        Future<Integer> future = executor.submit(() -> {
            Account account = new Account();
            account.setId(user.getId());
            int result2 = accountService.add(account);
            if (user.getId().intValue() == 11) {
                throw new RuntimeException("account执行异常");
            }
            return result2;
        });
        int result = userMapper.add(user);
        try {
            Integer integer = future.get();
        } catch (InterruptedException e) {
            throw new RuntimeException("子线程account执行中断异常");
        } catch (ExecutionException e) {
            throw new RuntimeException("子线程account执行异常");
        }
        return result;
}

这样当子线程account操作出现异常时,主线程是可以进行回滚的。

2.2.1 解决子线程出现异常,让父子线程都能够回滚。

上述解决还存在一个问题,那就是虽然父线程能够回滚了,但是子线程还是不能进行回滚。

原理是由于spring的事务管理也是采用jdk和cglib当方法进入addV2中,里面的逻辑就是Userservice对象本身执行的方法了,所以需要让spring再对子线程中的方法能进行代理。

案例:

SpringUtil

@Component
public class SpringUtil implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public IUserService getUserService(){
        return applicationContext.getBean(IUserService.class);
    }

    public <T> T get(Class<T> clazz){
        return applicationContext.getBean(clazz);
    }
}

UserService

@Override
public Integer addV3(User user) {
    Future<Integer> future = executor.submit(() -> {
        IUserService proxyUserService = springUtil.getUserService();
        Integer result2 = proxyUserService.addAccount(user.getId());
        return result2;
    });
    int result = userMapper.add(user);
    try {
        Integer integer = future.get();
    } catch (InterruptedException e) {
        throw new RuntimeException("子线程account执行中断异常");
    } catch (ExecutionException e) {
        throw new RuntimeException("子线程account执行异常");
    }
    return result;
}

@Override
public Integer addAccount(int id){
    Account account = new Account();
    account.setId(id);
    int result2 = accountService.add(account);
    if (id == 11) {
        throw new RuntimeException("account执行异常");
    }
    return result2;
}
  1. 首先设计一个springUtil来获取spring容器中的对象。
  2. 在线程池的submit方法中获取,此时的proxyUserService就是被代理的独享(jdk或cglib),再执行添加account方法。这个方法就会被spring事务管理,从而实现回滚。

你可能感兴趣的:(并发编程,线程池,java,spring,经验分享)