一种多线程设计思路

Posted helloworldplus

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一种多线程设计思路相关的知识,希望对你有一定的参考价值。

 

一、为什么设计多线程

  举个例子说明多线程的作用。比如有100个人去食堂打菜,如果只有一个窗口,那么所有人都需要在这个窗口进行排队,一个打完才能排到下一个,如果每个人打菜需要1分钟,这100个人打完菜总耗时就是100分钟,这就类似程序中的单线程。如果有5个打菜窗口,那么就可以每次5个人几乎同时进行打菜,相当于把100个人分成5个队,这样打菜的时间就减少了5倍,这100个人打完菜总耗时差不多为20分钟。这就类似于程序中使用了多线程。所以,使用多线程对用户体验的提升毋庸置疑。

  类似,物业软件中也有很多场景可以使用多线程。如多线程远程开门(同时开整个小区所有的门)、多线程下载卡、多线程下载设备参数、多线程删除卡等。

二、多线程的设计思路

  本文旨在设计一个通用型的多线程Http客户端。既然是通用,肯定是要兼容各种数据类型,而json字符串恰好能承担这个重任。另一方面,几乎所有的业务执行都需要得到执行结果反馈,故多线程的设计还需要考虑业务上的阻塞。这篇多线程程序阻塞核心算法如下:

    public List<String> send() {
        for(String data : jsonArray) {
            threadPool.execute(new MultiThread(url, data));
        }
        threadPool.shutdown();
        while (!threadPool.isTerminated()) {
        }
        return jsonArrayResponse;
    }

  从上述程序可看出,多线程的创建及执行使用了线程池做管理,而ExecutorService中的shutdown和isTerminated方法结合起来就可以实现多线程阻塞。

  其次,对于某些多线程业务,可能还需要依赖外部资源。如物业软件的门禁记录转发,由于设备门禁记录和抓拍的图片数据是异步上传的,通常记录会先到,抓拍的图片会延迟一两秒才上报完成。而转发给第三方的门禁数据中需要包含抓拍图片,那怎么办呢?本文同样提供了解决办法,如下:

  (1)转发门禁记录时,使用LockUtil的lock方法(key为图片路径)将转发线程锁住。

  (2)图片数据上报到物业中心时,先使用LockUtil的setParam方法把图片数据设置到缓存中,再使用LockUtil的unlock方法(key为图片路径)将图片解锁。

  (3)解锁后的门禁记录线程,使用LockUtil的getParma方法把图片数据从缓存中读出来,并添加到记录的属性中,就可了转发一条完整的门禁记录到第三方了。

核心算法如下:

        @Override
        public void run() {
            super.run();
            // 若线程有锁,此处处理线程锁中的参数。
            if (hasLock) {
                // 使用LockUtil的lock方法(key为图片路径)将转发线程锁住
                LockUtil.getInstance().lock(lockKey, 20);
                // 使用LockUtil的getParma方法把图片数据从缓存中读出来
                String param = (String)LockUtil.getInstance().getParam(lockKey);
                // 把参数添加到记录的属性中
                if (StringUtils.isNotBlank(param)) {
                    JSONObject obj = JSON.parseObject(data);
                    obj.put(lockParamKey, param);
                    data = JSON.toJSONString(obj);
                }
            }
            String res = HttpRequest.post(url).body(data).execute().body();
            jsonArrayResponse.add(res);
        }

  上述设计方法看似已经没什么问题,其实不然,由于应答数据是从多线程中逐条添加的,并且使用了ArrayList,而ArrayList是线程不安全的(ArrayList线程为什么不安全,请移步到https://blog.csdn.net/u012859681/article/details/78206494)。要解决ArrayList的线程安全问题其实很简单,只需要将多线程中的ArrayList改成如下写法即可:

List<String> jsonArrayResponse = Collections.synchronizedList(new ArrayList<>());

三、程序摘要

package com.leelen.ehome.multithread;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.leelen.ehome.utils.LockUtil;
import com.xiaoleilu.hutool.http.HttpRequest;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Http多线程客户端
 *
 * @Author:Jack
 */
public class HttpMultithreadClient {
    /**
     * json字符串数组,即需要发送的数据。
     */
    private List<String> jsonArray;
    /**
     * 发送目的地址:url
     */
    private String url;
    /**
     * 是否对线程加锁,默认否
     */
    private boolean hasLock = false;
    /**
     * key,线程锁秘钥
     */
    private String lockKey;
    /**
     * 从锁中读出的参数名称
     */
    private String lockParamKey;

    /**
     * json格式应答数组数据
     */
    private List<String> jsonArrayResponse = Collections.synchronizedList(new ArrayList<>());
    /**
     * 线程池
     */
    private ExecutorService threadPool = Executors.newFixedThreadPool(10);

    /**
     *
     * @param jsonArray json格式数组(数据体)
     * @param url   发送地址url
     * @param hasLock   是否加锁,若加锁则继续处理后两个参数
     * @param lockKey   若加锁则该参数必填,否则可为null
     * @param lockParamKey 若加锁则该参数必填,否则可为null
     */
    public HttpMultithreadClient(List<String> jsonArray, String url, boolean hasLock, String lockKey, String lockParamKey) {
        this.jsonArray = jsonArray;
        this.url = url;
        this.hasLock = hasLock;
        this.lockKey = lockKey;
        this.lockParamKey = lockParamKey;
    }

    /**
     * 多线程发送
     * @return
     */
    public List<String> send() {
        for(String data : jsonArray) {
            threadPool.execute(new MultiThread(url, data));
        }
        threadPool.shutdown();
        while (!threadPool.isTerminated()) {
           // 业务上需要等待执行结果,故此处多线程创建完毕后,需要阻塞等待执行结果的加载。
        }
        return jsonArrayResponse;
    }

    /**
     * 多线程实现类
     */
    class MultiThread extends Thread {
        /**
         * 发送目的地址:url
         */
        private String url;
        /**
         * 数据
         */
        private String data;

        public MultiThread(String url, String data) {
            this.url = url;
            this.data = data;
        }

        @Override
        public void run() {
            super.run();
            // 若线程有锁,此处处理线程锁中的参数。
            if (hasLock) {
                LockUtil.getInstance().lock(lockKey, 20);
                String param = (String)LockUtil.getInstance().getParam(lockKey);
                if (StringUtils.isNotBlank(param)) {
                    JSONObject obj = JSON.parseObject(data);
                    obj.put(lockParamKey, param);
                    data = JSON.toJSONString(obj);
                }
            }
            String res = HttpRequest.post(url).body(data).execute().body();
            jsonArrayResponse.add(res);
        }
    }
}

  

以上是关于一种多线程设计思路的主要内容,如果未能解决你的问题,请参考以下文章

多个请求是多线程吗

另一种多线程核心数据使用

一种多线程变量区域锁的实现方法

JAVA三种多线程实现方法和应用总结

java 两种多线程实现

一种多叉树的实现,提供树形结构打印,树转表输出等功能