开启多线程异步执行踩得坑

背景

我想实现一个点赞收藏功能,这个数据是存到redis中,然后通过定时任务给刷到数据库里面去的一个数据,在这个过程,我想通过异步的方式来实现一个操作日志的记录,所以就踩了坑

代码

java 复制代码
@Service
@Slf4j
public class VideoOperateServiceImpl implements VideoOperateService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private VideoOperateMapper videoOperateMapper;

    private BlockingQueue<VideoOperate> videoTasks = new ArrayBlockingQueue<>(1024 * 1024);
    private static final ExecutorService VIDEO_OPERATE_EXECUTOR = Executors.newSingleThreadExecutor();


/**
     * 这个方法的作用是保证在这个类初始化的后,就可以开始执行子线程的调用
     * 因为要一开始就要判断队列中是否有信息可用,可用就执行
     */
    @PostConstruct
    private void init() {
        VIDEO_OPERATE_EXECUTOR.submit(new VideoOperateHandler());
    }
    private VideoOperateService proxy;
    @Override
    public Integer like(Long videoId) {
        Long userId = UserHolder.getUser().getId();
        String isLikeKey = RedisConstants.VIDEO_LIKE + videoId;
        String likeCountKey = RedisConstants.VIDEO_LIKE_COUNT+videoId;
        Boolean isLike = stringRedisTemplate.opsForSet().isMember(isLikeKey, String.valueOf(userId));
        //获取点赞数
        String s = stringRedisTemplate.opsForValue().get(likeCountKey);
        Integer likeCount = Integer.valueOf(s);
        //生成对象
        VideoOperate videoOperate = createVideoOperate(videoId,userId,0);
        //已经点赞
        if(isLike){
            //总的点赞数-1
            stringRedisTemplate.opsForValue().set(likeCountKey,String.valueOf(likeCount-1));
            //挪出set列表
            stringRedisTemplate.opsForSet().remove(isLikeKey, String.valueOf(userId));
            videoOperate.setIsLike(String.valueOf(0));
            videoTasks.add(videoOperate);
            //获取代理对象,由于异步的线程代理对象不是当前线程的对象,所以需要将现在的代理对象传进去
            proxy = (VideoOperateService) AopContext.currentProxy();
            //返回0表示 要取消点赞
            return Integer.valueOf(0);
        }
        //没有点赞
        //总的点赞数+1
        stringRedisTemplate.opsForValue().set(likeCountKey,String.valueOf(likeCount+1));
        //加入set列表
        stringRedisTemplate.opsForSet().add(isLikeKey,String.valueOf(userId));
        videoOperate.setIsLike(String.valueOf(1));
        //返回1表示点赞成功
        //异步执行对于数据库的操作
        videoTasks.add(videoOperate);
        return Integer.valueOf(1);
    }

    private VideoOperate createVideoOperate(Long videoId, Long userId, int type) {
        VideoOperate videoOperate = new VideoOperate();
        videoOperate.setVideoId(videoId);
        videoOperate.setUserId(userId);
        videoOperate.setOperateType(String.valueOf(type));
        return videoOperate;
    }

    @Override
    public Integer collect(Long videoId) {
        Long userId1 = UserHolder.getUser().getId();
        String userId = String.valueOf(userId1);
        String isCollectKey = RedisConstants.VIDEO_COLLECT + videoId;
        String collectCountKey = RedisConstants.VIDEO_COLLECT_COUNT+videoId;
        Boolean isCollect = stringRedisTemplate.opsForSet().isMember(isCollectKey, userId);
        //获取收藏数
        String s = stringRedisTemplate.opsForValue().get(collectCountKey);
        Integer collectCount = Integer.valueOf(s);
        //创建对象
        VideoOperate videoOperate = createVideoOperate(videoId,userId1,1);
        //已经收藏
        if(isCollect){
            //总的收藏数-1
            stringRedisTemplate.opsForValue().set(collectCountKey,String.valueOf(collectCount-1));
            //挪出set列表
            stringRedisTemplate.opsForSet().remove(isCollectKey,userId);
            videoOperate.setIsCollect(String.valueOf(0));
            videoTasks.add(videoOperate);
            //返回0表示 要取消收藏
            return Integer.valueOf(0);
        }
        //没有收藏
        //总的收藏数+1
        stringRedisTemplate.opsForValue().set(collectCountKey,String.valueOf(collectCount+1));
        //加入set列表
        stringRedisTemplate.opsForSet().add(isCollectKey,userId);
        videoOperate.setIsCollect(String.valueOf(1));
        videoTasks.add(videoOperate);
        //返回1表示收藏成功
        return Integer.valueOf(1);

    }
    private class VideoOperateHandler implements Runnable{
        @Override
        public void run() {
            //子线程,开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能
            while (true) {
                //1.获取队列中的订单信息
                try {
                    //从阻塞队列中获取
                    VideoOperate v = videoTasks.take();
                    //2.操作数据库
                    proxy.handlerData(v);
                } catch (Exception e) {
                    log.error("数据异常", e);
                }
            }
        }
    }
    @Override
    @Transactional
    public void handlerData(VideoOperate v) {
        //如果是已经点赞,或者已经收藏 那就将原来数据删掉
        if(("0".equals(String.valueOf(v.getIsLike())))||("0".equals(String.valueOf(v.getIsCollect())))){
            videoOperateMapper.delete(v.getVideoId(), v.getOperateType());
        }
        videoOperateMapper.add(v);
    }
}

踩的坑

这个多线程异步实现的思路,首先就是去操作redis,然后将要操作的数据丢到阻塞队列中,当这个类初始化的时候(init())就会执行该线程池,然后就从阻塞队列中获取要的数据,去对数据库进行修改,但我的redis的数据会进行修改,但是Mysql数据库的一直都不会进行修改,也不会报错

一、多线程修改数据库的问题

使用多线程修改数据库的时候要注意事务的问题,Spring不会默认为我们开启事务,我们可以通过在方法上或者类上加上@Transactional 来代表要介入Spring的事务

二、事务失效的问题

我们在调用被@Transactional注解的方法的时候要注意事务失效的问题,如果直接调用,那么默认会以this.method()的方式进行调用(this对象,在编译的时候就已经被加载到栈中的局部变量表中了)。而我们在调用bean的时候要注意是要用交给Spring进行管理的对象,所以,才会使用代理对象来生成一个proxy对象,来调用方法,这样就防止了Spring事务的失效

小结

Spring框架对于多线程操作数据,为了保证数据的原子性和隔离性,在一定的情况下,Spring会阻止我们的多线程操作数据库,所以只有在保证事务的情况下,才会进行数据库的修改操作。

相关推荐
北极无雪12 分钟前
Spring源码学习(拓展篇):SpringMVC中的异常处理
java·开发语言·数据库·学习·spring·servlet
VXbishe19 分钟前
(附源码)基于springboot的“我来找房”微信小程序的设计与实现-计算机毕设 23157
java·python·微信小程序·node.js·c#·php·课程设计
YONG823_API43 分钟前
电商平台数据批量获取自动抓取的实现方法分享(API)
java·大数据·开发语言·数据库·爬虫·网络爬虫
扬子鳄0081 小时前
java注解的处理器
java
Amagi.1 小时前
Spring中Bean的作用域
java·后端·spring
2402_857589361 小时前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
繁依Fanyi1 小时前
旅游心动盲盒:开启个性化旅行新体验
java·服务器·python·算法·eclipse·tomcat·旅游
J老熊1 小时前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
蜜桃小阿雯1 小时前
JAVA开源项目 旅游管理系统 计算机毕业设计
java·开发语言·jvm·spring cloud·开源·intellij-idea·旅游
CoderJia程序员甲1 小时前
重学SpringBoot3-集成Redis(四)之Redisson
java·spring boot·redis·缓存