需求:编写接口 ,实现请求五个不同的接口,然后聚合5个结果,并返回结果。由于这个接口内要请求多次,如果挨个请求响应速度会很慢,所以采取多线程编程,并且使用循环栅栏以及结果回调,等五个都请求完再返回。

1.CyclicBarrier 循环栅栏

作用:让所有线程都等待完成后才会继续下一步行动。这里的demo采用定长线城池进行创建,然后调用线程方法,最后再循环栅栏的最终方法里关闭线城池。

// newFixedThreadPool:定长线城池。采用队列缓存线程的模式,线程池数和最大线程数一致。count表示创建的线程数
ExecutorService threadPool = Executors.newFixedThreadPool(count);

// count表示几个线程等待完成后,执行BarrierRun方法。threadPool传递线城池
CyclicBarrier cyclic = new CyclicBarrier(count, new BarrierRun(threadPool));
/**
 * 类描述:模拟多线程请求http
 *
 * @ClassName CyclicBarrierDemo
 * @Author ward
 */
public class CyclicBarrierDemo {
    public static class HttpReqRunnable implements Runnable {
        private final CyclicBarrier cyclicBarrier;
        private String webUrl;

        public HttpReqRunnable(CyclicBarrier cyclicBarrier, String webUrl) {
            this.cyclicBarrier = cyclicBarrier;
            this.webUrl = webUrl;
        }

        @Override
        public void run() {
            try {
                doWork();
                cyclicBarrier.await();
                System.out.println("");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }

        private void doWork() throws InterruptedException {
            //模拟请求过程
            Thread.sleep(Math.abs(new Random().nextInt() % 10000));
            System.out.println("请求结果:【" + this.webUrl + "】完成");
        }
    }

    public static class BarrierRun implements Runnable {
        private final ExecutorService threadPool;

        public BarrierRun(ExecutorService threadPool) {
            this.threadPool = threadPool;
        }

        @Override
        public void run() {
            System.out.println("所有接口都请求完成了,关闭线城池");
            //关闭线程池
            this.threadPool.shutdown();
        }
    }

    public static void main(String[] args) {
        final int count = 2;
        String url1 = "https://www.baidu.com/";
        String url2 = "https://cn.bing.com/";
        ExecutorService threadPool = Executors.newFixedThreadPool(count);
        CyclicBarrier cyclic = new CyclicBarrier(count, new BarrierRun(threadPool));

        //显式创建线程
        Thread oneHttp = new HttpReqRunnable(cyclic, url1);
        Thread twoHttp = new HttpReqRunnable(cyclic, url2);

        //newFixedThreadPool:定长线城池。采用队列缓存线程的模式,线程池数和最大线程数一致。
        threadPool.execute(oneHttp);
        threadPool.execute(twoHttp);
    }
}

2.Callback 回调函数

使用多线程增加回调函数,使得线程中的结果可以在主线程中显示。先写一个回调函数泛型类,然后在线程中增加泛型类的参数,接着在执行方法里回调,最后再主线程中进行重写。

//回调函数接口
public interface Callback<T> {
	void process(T t);
}

requests多线程 多线程发送http请求_java


requests多线程 多线程发送http请求_网络协议_02


完整源码

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.*;

/**
 * 类描述:模拟多线程请求http
 *
 * @ClassName CyclicBarrierDemo
 * @Author ward
 */
public class CyclicBarrierDemo {
    public static class HttpReqRunnable implements Runnable {
        private final CyclicBarrier cyclicBarrier;
        private final Callback<String> callback;
        private String webUrl;

        public HttpReqRunnable(CyclicBarrier cyclicBarrier, String webUrl, Callback<String> callback) {
            this.cyclicBarrier = cyclicBarrier;
            this.webUrl = webUrl;
            this.callback = callback;
        }

        @Override
        public void run() {
            try {
                doWork();
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }

        private void doWork() throws InterruptedException {
            //模拟请求过程
            Thread.sleep(Math.abs(new Random().nextInt() % 10000));
            String res = "请求结果:【" + this.webUrl + "】完成";
            System.out.println(res);
            if (callback != null) {
                this.callback.process(res);
            }
        }
    }

    public static class BarrierRun implements Runnable {
        private final ExecutorService threadPool;

        public BarrierRun(ExecutorService threadPool) {
            this.threadPool = threadPool;
        }

        @Override
        public void run() {
            System.out.println("所有接口都请求完成了,关闭线城池");
            //关闭线程池
            this.threadPool.shutdown();
        }
    }

    /**
     * 回调函数
     *
     * @param <T>
     */
    public interface Callback<T> {
        void process(T t);
    }

    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        final int count = 2;
        String url1 = "https://www.baidu.com/";
        String url2 = "https://cn.bing.com/";
        ExecutorService threadPool = Executors.newFixedThreadPool(count);
        CyclicBarrier cyclic = new CyclicBarrier(count, new BarrierRun(threadPool));

        //显式创建线程
        Thread oneHttp = new HttpReqRunnable(cyclic, url1, new Callback<String>() {
            @Override
            public void process(String s) {
                System.out.println("我是主线程:" + s);
            }
        });
        Thread twoHttp = new HttpReqRunnable(cyclic, url2, new Callback<String>() {
            @Override
            public void process(String s) {
                System.out.println("我是主线程:" + s);
            }
        });
        //newFixedThreadPool:定长线城池。采用队列缓存线程的模式,线程池数和最大线程数一致。
        threadPool.execute(oneHttp);
        threadPool.execute(twoHttp);
    }
}

3.实现最终需求

目前可以实现在主线程中获取结果,但是对于一个接口来说,需要等待请求baidu和biing之后进行打印。但采用线程,主线程的方法是一直往下执行的,所以采用CyclicBarrier设置障碍点来实现。所以CyclicBarrier的parties要比实际线程数多1,然后在实际线程执行完后用CyclicBarrier卡住。(ps:修改main方法即可)

requests多线程 多线程发送http请求_线程池_03

public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        //比线程数+1
        final int count = 3;
        String url1 = "https://www.baidu.com/";
        String url2 = "https://cn.bing.com/";
        Map<String, String> result = new HashMap<>(16);
        ExecutorService threadPool = Executors.newFixedThreadPool(count);
        CyclicBarrier cyclic = new CyclicBarrier(count, new BarrierRun(threadPool));

        //显式创建线程
        Thread oneHttp = new HttpReqRunnable(cyclic, url1, new Callback<String>() {
            @Override
            public void process(String s) {
                result.put("baidu", s);
                System.out.println("我是主线程:" + s);
            }
        });
        Thread twoHttp = new HttpReqRunnable(cyclic, url2, new Callback<String>() {
            @Override
            public void process(String s) {
                result.put("bing", s);
                System.out.println("我是主线程:" + s);
            }
        });
        //newFixedThreadPool:定长线城池。采用队列缓存线程的模式,线程池数和最大线程数一致。
        threadPool.execute(oneHttp);
        threadPool.execute(twoHttp);

        cyclic.await();
        System.out.println("主线程最终结果:" + result);
    }

番外

a.不要显示创建线程,请使用线城池创建。这是最佳的原始线城池创建方法。



  • corePoolSize:核心线程数(最小可以同时运行的线程数量)
  • maximumPoolSize:线程池的最大线程数
  • keepAliveTime:当前线程数>核心线程数时,它们差值的绝对值为多余线程。多余的线程存活时长
  • unit:上面keepAliveTime的实践单位
  • workQueue:超过核心线程数后,新任务存储的队列(阻塞队列)
  • threadFactory:线程工厂(取名字)
  • handler:饱和(又称拒绝)策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
public ThreadPoolExecutor(int corePoolSize,
						  int maximumPoolSize,
						  long keepAliveTime,
						  TimeUnit unit,
						  BlockingQueue<Runnable> workQueue,
						  ThreadFactory threadFactory,
						  RejectedExecutionHandler handler) {
	this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
		Executors.defaultThreadFactory(), defaultHandler);
}

requests多线程 多线程发送http请求_网络协议_04

b.之前直接采用new Thread,这里说一下弊端。

  • 每次new Thread新建对象性能差。
  • 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
  • 缺乏更多功能,如定时执行、定期执行、线程中断。
//创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
new CachedThreadPool

//创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
new FixedThreadPool

//创建一个计划线程池,支持定时及周期性任务执行。
new ScheduledThreadPool

//创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
new SingleThreadExecutor

c.尽量使用公共线城池

每次new ThreadPoolExecutor,又没有关闭很有可能把内存用完,所以采用公共线城池,写个公共线城池类非必要不要创建线城池,或者有就关掉。直接用HuTool的工具也行:ThreadUtil.execute(线程名)

/**
     * 初始化全局线程池,直接关闭的做法
     */
    synchronized public static void init() {
        if (null != threadPoolExecutor) {
            threadPoolExecutor.shutdownNow();
        }
        threadPoolExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>());
    }

    /**
     * 初始化全局定长线程池,有就不创建的原则
     */
    synchronized public static ThreadPoolExecutor initFixed(int threadSize, ThreadFactory threadFactory) {
        if (null != threadPoolExecutor) {
            //threadPoolExecutor.shutdownNow();
            return threadPoolExecutor;
        }
        threadPoolExecutor = new ThreadPoolExecutor(threadSize, threadSize, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(), threadFactory);
        return threadPoolExecutor;
    }

d.多线程超时机制

进行多线程请求,要考虑到超时的处理,否则每个线程执行异常,都要等待很久,客户体验极差。
这里设置屏障点超时机制为5s,任意一个线程超过5s就抛异常。

cyclicBarrier.await(5, TimeUnit.SECONDS);