@Async注解添加上线程锁解决线程冲突

文章讨论了在使用@Async进行异步处理时可能出现的线程安全问题,例如并发更新数据库导致的数据不一致。为了解决这个问题,提出了使用ReentrantLock作为同步机制,确保同一时间只有一个线程能执行特定操作。进一步,文章还提到了在分布式服务场景下可能需要使用Redis实现分布式锁。此外,对比了乐观锁(版本控制)和悲观锁的优缺点,指出在某些情况下,悲观锁可能导致数据丢失。
摘要由CSDN通过智能技术生成

在使用@Async注解时,假如出现线程冲突问题时怎么办呢?
比如下面的场景,在添加异步注解后可能会出现的问题
假设同时开启两个线程A和B,读的数在数据库里一开始为1,每次读后应当+1,那么两个线程读后应当最后会变为3

A(取1B(取1A(根据12B(根据12

但是由于是异步处理,会出现上面的结果,最终会变为2。为此引入解决的办法,添加一个异步锁Lock
service层写法:


public interface ITestService {
	
	 @Async
    void test(String schoolId) 
}

serviceImpl层写法

@Service
@Slf4j
public class TestServiceImpl implements ITestService {
	private ReentrantLock reentrantLock = new ReentrantLock();

	@Override
    public void test(String schoolId) throws IllegalAccessException {
        // 获取锁,如果锁不可用,则出于线程调度的目的,当前线程将被禁用,并且在获取锁之前处于休眠状态
        reentrantLock.lock();
        try{
			...
		} catch (Exception e){
            throw new ServiceException("业务异常");
        }finally {
        	// 释放锁
            reentrantLock.unlock();
        }
}

这样改造的结果就变成了

A(取1A(根据12B(取2B(根据23

此处理方法仅适用于部署了单个服务的场景。如果是分布式服务那要另外考虑引入redis分布式。

进阶修改

假如现在一个业务,要异步方法里面刷新某个表的记录,那么一条记录应当被上锁,
而不是整个方法都排队。
假如A线程和B线程同时改表X的x1记录,那么A和B应当排队,而另一条记录x2则是空闲的不需要排队的。
那么可以这样实现,加多一个map管理

@Service
@Slf4j
public class TestServiceImpl implements ITestService {
	private Map<String,ReentrantLock> lockMap = new HashMap<>();

	@Override
    public void test(String schoolId) throws IllegalAccessException {
        // 获取锁,如果锁不可用,则出于线程调度的目的,当前线程将被禁用,并且在获取锁之前处于休眠状态
        // 根据指定的key获取指定的锁,没有就新建一个
		if( !this.lockMap.containsKey(schoolId) ){
           this.lockMap.put(schoolId,new ReentrantLock());
       }
       // 注意这里不能获取为局部变量,否则地址则不是原来的map里的value地址
        this.lockMap.get(schoolId).lock();
        try{
			...
		} catch (Exception e){
            throw new ServiceException("业务异常");
        }finally {
        	// 释放锁
             this.lockMap.get(schoolId).unlock();
             // 如果没有等待的线程队列则移除此key
             if( !this.lockMap.get(schoolId).hasQueuedThreads() ){
                lockMap.remove(schoolId);
            }
        }
}

为什么不使用悲观锁?

如果加个字段,如version,则每次更新数据要version相等才可更新。但是如果引入则会出现另一个问题,
假如你的事务是不可重复读的(事务B在重复读取中,读到的数据不一样,是因为A在B读取的期间改掉了数据)

假设version一开始为1,每次更新后会+1

A(取1B(取1A(根据12B(无法修改数据,因为此时version为2

那样数据会是2,我们需要的数据应当是3,这样就造成数据的遗失了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 @Scheduled 注解添加 @Async 注解可以实现定时任务的异步执行。@Async 注解可以用于标记一个方法为异步方法,表示该方法将在一个独立的线程中执行,而不会阻塞当前线程。 下面是一个示例: ```java import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class MyScheduledTask { @Async @Scheduled(fixedRate = 5000) // 每隔5秒执行一次 public void myAsyncTask() { // 异步执行的任务逻辑 System.out.println("异步任务开始执行..."); // ... System.out.println("异步任务执行完成!"); } } ``` 在上述示例中,我们在定时任务方法上同时添加了 @Async 和 @Scheduled 注解。@Scheduled 注解用于配置定时任务的执行频率,这里使用 fixedRate 表示每隔5秒执行一次。@Async 注解表示该方法将异步执行。 需要注意的是,为了使 @Async 注解生效,还需要在 Spring Boot 的主类上添加 @EnableAsync 注解。 ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } ``` 这样配置之后,定时任务将在独立的线程中异步执行,不会阻塞当前线程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值