SpringBoot使用多线程处理任务

业务场景

如果,我先用Key*,去模糊匹配Redis的key,返回出来的key,有3000个,这时候我需要一个for循环读取key的数据,3000条,并且把读取出来的数据,写入同一个List。只用一个for循环同步处理,就会花费很长时间,但是,如果使用多线程异步去读取,那么,时间会大大缩减。

代码实现

SpringBoot应用中需要添加@EnableAsync注解,来开启异步调用,一般还会配置一个线程池,异步的方法交给特定的线程池完成,如下:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池初始化类
 *
 * @author huangJunHao
 * @date 2022/3/18
 */
@Configuration
@EnableAsync
public class AsyncConfiguration {

    @Value(value = "${threadPool.corePoolSize}")
    private int corePoolSize;
    @Value(value = "${threadPool.maxPoolSize}")
    private int maxPoolSize;
    @Value(value = "${threadPool.queueCapacity}")
    private int queueCapacity;
    @Value(value = "${threadPool.keepAliveSeconds}")
    private int keepAliveSeconds;
    @Value(value = "${threadPool.threadNamePrefix}")
    private String threadNamePrefix;

    @Bean("doSomethingExecutor")
    public Executor doSomethingExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数:线程池创建时候初始化的线程数
        executor.setCorePoolSize(corePoolSize);
        // 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(maxPoolSize);
        // 缓冲队列:用来缓冲执行任务的队列
        executor.setQueueCapacity(queueCapacity);
        // 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(keepAliveSeconds);
        // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix(threadNamePrefix);
        // 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

}

使用的方式非常简单,在需要异步的方法上加@Async注解

import com.alibaba.fastjson.JSONObject;
import com.watsons.onstore.common.dto.Result;
import com.watsons.onstore.common.utils.EmptyUtil;
import com.watsons.onstore.common.utils.ResultUtil;
import com.watsons.onstore.redis.config.RedisKey;
import com.watsons.onstore.redis.pojo.User;
import com.watsons.onstore.redis.service.IRedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;

/**
 * 测试类
 *
 * @author huangJunHao
 * @date 2022/3/18
 */
@RestController
public class TestController {

    @Autowired
    private IRedisService redisService;

    @Autowired
    private AsyncService asyncService;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private List userList;


    /**
     * 测试插入redis
     */
    @RequestMapping("/set/redis/list")
    void setRedisList() {
        for (int i = 0; i < 1000; i++) {
            String key = RedisKey.getRedisKey("6666", RedisKey.PICK_ORDER_LIST, i + "", System.currentTimeMillis() + "");
            User user = new User();
            user.setEmpNo("EmpNo" + i);
            user.setEmail("Email" + i);
            redisService.set(key, JSONObject.toJSONString(user));
        }
    }

    /**
     * 测试根据模糊key获取redis数据,返回一个List
     */
    @RequestMapping("/get/redis/list/thread")
    Result getRedisListThread() {
        //要把list变成线程安全的容器,否则会数据不正确
        userList = Collections.synchronizedList(new ArrayList<>());
        String key = RedisKey.getRedisKey("6666", RedisKey.PICK_ORDER_LIST, "*");
        Set keys = redisService.keys(key);
        System.out.println("keys:" + keys.size());
        List futureList = new ArrayList<>();
        if (EmptyUtil.notEmpty(keys)) {
            int j = 1;
            for (String s : keys) {
                try {
                    j++;
                    //下面这个方法在方法上使用了@Async注解的
                    Future future = asyncService.doSomething(s, j);
                    futureList.add(future);

                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
            //判断进程是否全部结束
            while (true) {
                if (null != futureList) {
                    boolean isAllDone = true;
                    for (Future future : futureList) {
                        if (null == future || !future.isDone()) {
                            isAllDone = false;
                        }
                    }
                    if (isAllDone) {
                        break;
                    }
                }
            }
        }
        System.out.println("userList:" + userList.size());
        return ResultUtil.renderSuccess(userList);
    }

    @Service
    public class AsyncService {
        @Async("doSomethingExecutor")
        Future doSomething(String s, int i) {
            //logger.info("开始获取user,{}", i);
            try {
                User entity = redisService.getEntity(s, User.class);
                userList.add(entity);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //返回需要用AsyncResult类,我也没试过其他的
            return new AsyncResult(null);
        }
    }

    /**
     * 测试根据模糊key获取redis数据,返回一个List
     */
    @RequestMapping("/get/redis/list")
    Result getRedisList() {
        String key = RedisKey.getRedisKey("6666", RedisKey.PICK_ORDER_LIST, "*");
        try {
            List list = redisService.getFuzzyList(key, User.class);
            System.out.println("getRedisList:" + list.size());
            return ResultUtil.renderSuccess(list);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ResultUtil.renderSuccess();
    }
}

结论

getRedisList:这是一个同步方法,从redis获取1000条数据耗时20S
getRedisListThread:这是一个异步方法,从redis获取1000条数据耗时1.5S

注意事项

@Async注解会在以下几个场景失效,也就是说明明使用了@Async注解,但就没有走多线程。

1、异步方法使用static关键词修饰;
2、异步类不是一个Spring容器的bean(一般使用注解@Component和@Service,并且能被Spring扫描到);
3、SpringBoot应用中没有添加@EnableAsync注解;
4、在同一个类中,一个方法调用另外一个有@Async注解的方法,注解不会生效。原因是@Async注解的方法,是在代理类中执行的。

需要注意的是: 异步方法使用注解@Async的返回值只能为void或者Future及其子类,当返回结果为其他类型时,方法还是会异步执行,但是返回值都是null

你可能感兴趣的:(后端,缓存,JAVA,spring,boot,java,spring)