需求:编写接口 ,实现请求五个不同的接口,然后聚合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);
}
完整源码
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方法即可)
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);
}
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);