多线程使用实例:利用多线程进行网络访问以加快效率

之前看的多线程案例都是利用System.out.println() 来进行输出。那种其实简化了很多
刚好最近有利用多线程进行网络访问。来提升效率。记录下

背景:我负责的项目有个接口一直特别慢。经过分析:慢的原因是因为循环调用了同一个接口。我这个接口有两部分:一部分查询数据库。另一部分从远程服务获取数据。

我分析后给出的方案是:处理到循环调用。

  1. 查询数据库部分 将之前的类型getUser(int id)这种查询改成 listUser(List<String>)这种批量查询
  2. 将对远程的访问利用多个线程并行执行。

最终结果证明的方案可行的。成功将接口响应时间降了下来

这里介绍利用多线程访问的案例
下面的原装代码示例。还是比较看懂的

原理:串行一个接一个的访问太消耗时间了。于是考虑利用线程并行执行
我这里每一批次大概有10组数据。同时我需要每次向数据返回。那种异步的我觉得不适合我这种场景,以及我也不擅长(我觉得前者是主要原因,因为没有合适的常见,所以不擅长)

  • 我这里利用了ConcurrentLinkedQueue 来存储输入的参数。
  • ConcurrentHashMap来存储每一批获取的结果。
  • 然后CountDownLatch 来等待一批完成在进行下一步

具体的逻辑看代码把。我觉得还是结构还是比较清晰的

示例代码

构造一个单例的线程安全的线程池

package com.centanet.bizcom.thread;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.*;
/**
 * @Author: wbdengtt
 * @Date: 2020/10/27 9:34
 * 创建一个线程安全的 单例线程池
 */
public class ThreadPoolFactory {
    private static ExecutorService executorService = null;
    /**
     * 获取CPU核心数
     * IO密集型任务  = 一般为2*CPU核心数(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)
     * CPU密集型任务 = 一般为CPU核心数+1(常出现于线程中:复杂算法)
     * 混合型任务  = 视机器配置和复杂度自测而定
     */
    private static final int CORE_SIZE = Runtime.getRuntime().availableProcessors();
    private ThreadPoolFactory() {
    }
    // 创建一个线程安全的 单例线程池
    public static synchronized ExecutorService getBizComThreadPool() {
        if (executorService == null) {
            ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("ThreadPool-%d").build();
            executorService = new ThreadPoolExecutor(CORE_SIZE, CORE_SIZE * 2, 30L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(), threadFactory);
        }
        return executorService;
    }
}

线程池的使用

package com.centanet.bizcom.thread;
import com.centanet.bizcom.model.dto.KeyValueDTO;
import com.centanet.bizcom.model.vo.Pre400ParameterVO;
import com.centanet.bizcom.util.BizHelper;
import com.centanet.bizcom.util.CityHelper;
import com.centanet.bizcom.util.ConfigHelper;
import com.centanet.bizcom.util.HttpHelper;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
/**
 * @Author: wbdengtt
 * @Date: 2020/10/27 9:45
 * 用来帮助获取400长短号。这个线程没有通用性。仅能用来获取400长短号
 */
@Getter
@Setter
@Slf4j
public class CallThreadHelper {
    /**
     * 用来等待线程完成的阑珊
     */
    private CountDownLatch countDownLatch;
    /**
     * 用来接收结果
     */
    private ConcurrentHashMap<String, String> resultMap;
    /**
     * 输入参数
     */
    private ConcurrentLinkedQueue<KeyValueDTO> paramQueue;
    public Map<String, String> getResult(List<Pre400ParameterVO> pre400ParameterVOS, String cityCode) {
        paramQueue = new ConcurrentLinkedQueue<>();
        resultMap = new ConcurrentHashMap<>(16);
        for (Pre400ParameterVO pre : pre400ParameterVOS) {
            if (pre == null || pre.getKey() == null || pre.getPostId() == null || pre.getStaff() == null) {
                continue;
            }
            KeyValueDTO keyValueDTO = new KeyValueDTO(pre.getKey(), BizHelper.get400Param(pre), cityCode);
            paramQueue.add(keyValueDTO);
        }
        int sizeTemp = paramQueue.size();
        countDownLatch = new CountDownLatch(sizeTemp);
        ExecutorService executorService = ThreadPoolFactory.getBizComThreadPool();
        for (int i = 0; i < sizeTemp; i++) {
            CallRunnable callRunnable = new CallRunnable();
            executorService.execute(callRunnable);
        }
        // 等待所有线程完成
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("await失败,请检查");
        }
        return resultMap;
    }
    class CallRunnable implements Runnable {
        /**
         * When an object implementing interface <code>Runnable</code> is used
         * to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p>
         * The general contract of the method <code>run</code> is that it may
         * take any action whatsoever.
         *
         * @see Thread#run()
         */
        @Override
        public void run() {
            while (!paramQueue.isEmpty()) {
                KeyValueDTO keyValueDTO = paramQueue.poll();
                if (keyValueDTO == null) {
                    return;
                }
                // 获取远程结果,并将结果装载到resultMap
                resultMap.put(keyValueDTO.getKey(), getPostResponse(keyValueDTO.getValue(), keyValueDTO.getCityCode()));
                countDownLatch.countDown();
            }
        }
        /**
         * 获取400 结果 这里是结果获取的直接结果。没有任何处理
         *
         * @param jsonParam 输入参数
         * @param cityCode  因为不能直接从CityHelper.getCityCode()获得cityCode。需要传递进来。不能获取的原因是这和controller那个已经不是一个线程了
         * @return 返回结果
         */
        private String getPostResponse(String jsonParam, String cityCode) {
            String url = ConfigHelper.get400Url(cityCode);
            try {
                return HttpHelper.getPostResponse(jsonParam, url, CityHelper.getCityen(cityCode));
            } catch (IOException e) {
                log.error("从400获取长短号发生异常{},访问的地址为{},城市{} ", e, url, CityHelper.getCityen(cityCode));
            }
            // 补偿机制 通过域名再拿一次
            try {
                url = ConfigHelper.get400StandbyAddress(cityCode);
                return HttpHelper.getPostResponse(jsonParam, url, CityHelper.getCityen(cityCode));
            } catch (IOException e) {
                log.error("从400获取长短号发生异常{},访问的地址为{},城市{} ", e, url, CityHelper.getCityen(cityCode));
            }
            //东西没拿到,向微信平台发送警报 这里关于警报的接口我这里无法调用。而且没必要。我后续根据结果是否是None来判断警报好了
            return "None";
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值