多线程下任务状态异常的问题解决

场景描述

在一个线程中将一批任务通过循环依次添加到LinkedBlockingQueue中,并将任务状态改为待执行。后台使用mysql数据库。然后另外有一个线程从这个queue中阻塞取出任务,并执行,这时将任务状态修改为执行中。

@Transactional
public void taskBatchSchedule(List<Integer>ids) {
	for (Integer id: ids) {
		Task task = getTask(id);
		queue.put(task);
		updateTaskWait(task);
	}
}

问题描述

在这个场景下,任务状态的变更有时候正常,有时出现问题:第一个任务状态一直已经执行但状态还为待执行,执行结束后任务状态修改为执行结束。接着第二个任务正常显示任务状态。

问题分析

这里有生产和消费两个线程,当生产线程循环去添加任务并连接数据库更新任务状态。当生产端还没结束,事务还未提交(一个方法视为一个事务)。此时,消费端已经取出任务队列中第一个任务去执行。当它去更改任务状态时,由于生产线程的事务还未提交。消费线程等待超时后便继续向后执行,任务状态修改失败。

在我这个场景中,第一个任务执行的时候最容易出现该问题,因为此时生产和消费线程同时竞争数据库资源,第二,或者后面的任务在执行的时候,生产线程已经结束,就不太会出现这个问题。

相关知识总结

事务范围

  • 当方法上标注了@Transactional注解时,整个方法体内的所有数据库操作都会在一个统一的事务范围内进行。
  • 这意味着事务会在方法执行开始时开启,并在方法正常完成时提交,或者在方法中抛出未被捕获的异常时回滚。

循环中的更新

  • 如果在一个循环中更新多条记录,这些更新操作都会在同一事务中进行。
  • 即使循环中有多个数据库操作,也只有在循环结束后整个方法执行完成时,事务才会提交。

锁和并发

  • 在循环中更新数据时,如果其他线程尝试同时更新相同的记录,那么这些线程将等待锁释放,或者因为锁等待超时而失败。
  • MySQL默认使用行级锁,在InnoDB存储引擎中,当一个事务正在更新一行数据时,它会对那行数据加上排他锁(X锁),阻止其他事务同时更新该行数据。

超时和异常处理

  • 通过@Transactional(timeout = seconds)来设置事务的最大执行时间。如果事务在此时间内没有完成,Spring会自动回滚事务并抛出异常。
  • 如果事务因为长时间等待锁而超时,MySQL会抛出LockWaitTimeoutException,Spring会将其转换为TransactionSystemException异常。捕获并处理TransactionSystemException异常,以确保应用程序能够优雅地处理这种情形。调整MySQL的innodb_lock_wait_timeout系统变量来配置数据库级别的锁等待超时时间。

问题解决

问题在于两个线程资源争夺所造成,所以这里我使用了ReentrantLock来同步生产和消费线程。另外,将生产线程里,循环更新任务状态改为批量更新,特别是在数据批次较大的时候,能够显著减少数据库操作次数,减少事务持续时间。

愿你我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值