JAVA并行异步编程线程池+FutureTask

场景

  最近,在做一个项目,涉及到与其他厂家接口对接。场景是这样的,通过一个标识获取该标识下的所有附件路径,然而第三方是要求先获取该标识下的所有文件Id,然后再通过文件Id一个一个的获取文件。

经协调无果,只能另想他法,于是采用了多线程处理,并返回结果。

 

 

 

 

思路

  java 在JDK1.5中引入一个新的并发包java.util.concurrent 该包专门为java处理并发而书写。

  在java中熟悉的使用多线程的方式为两种?继续Thread类,实现Runnale。两种方式简单方便。

  在Jdk1.5之后其实有第三种方式实现方式,采用并发包中的Callable接口 FuruteTask类 以及ExecutorService接口。

 

运行

  说新的实现方式之前先来说讨论一下传统的java执行过程,我这里就通过举例一个简单的例子来说明线程的执行方式。

传统方式

  首先一个简单的程序一个方法生成随机数,在生成随机数的方法执行中,睡眠1s模拟方法调用时候的耗时,把结果放进集合中,最后算到总结果。

java代码

public class TestMain {

        public static void main(String[] args) throws InterruptedException {
            long start = System.currentTimeMillis();
            TestMain count = new TestMain();
            List<Integer> res = new ArrayList<Integer>();
            res.add(count.random());
            res.add(count.random());
            res.add(count.random());
            res.add(count.random());
            int totle =0;
            for (int i = 0; i < res.size(); i++) {
                totle+=res.get(i);
            }
            long end = System.currentTimeMillis();
            System.out.println("运算结束 耗时:"+(end-start)+"ms  totle:"+totle );
            System.out.println("退出main线程!");
        }

        int random() throws InterruptedException{
            Thread.sleep(1000); //
            return new Random().nextInt(100);
        }
}

执行结果

 

 

 执行原理

在传统的编写中是单线程的操作,串行操作,当调用方法count.random(),main线程被阻塞起来,直到睡眠时间到达,自动唤醒main线程。 

 

 

 

那么有没有什么办法来减少main主线程的阻塞时间呢?能不能让这几个操作并行进行呢?如果是并行运行带来什么好处呢?

并行带来的好处,可以减少比较多的方法执行时间,如random()方法并行计算,也就是说main线程的阻塞只有1s,阻塞时间减少75%

java为我们提供了多线程机制,利用多线程我们可以实现方法的并行运算,实现多线程的办法,实现Runnable接口重新run,继承Thread 重写run;

因为run方法的并没有返回值,我们手动的去创建大量的线程并且维护线程是件很讨厌的事情,并且创建线程也是非常耗费资源的操作,能不能有一个池子来帮我们管理线程呢?

有没有一个类能够透明的去进行透明并发的异步操作呢?

这个在JDK1.5之前是没有的,在1,5之后出现了一个新包,专门为并发而开发的包,使用并发包中提供的类和接口,将很轻易的实现。并发编程。

 

 

采用ExecutorService

java代码

public class TestMain {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        new  TestMain().exec();
    }

    void exec() throws InterruptedException, ExecutionException{
        //进行异步任务列表
        List<FutureTask<Integer>> futureTasks = new ArrayList<FutureTask<Integer>>();
        //线程池 初始化十个线程 和JDBC连接池是一个意思 实现重用
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        long start = System.currentTimeMillis();
        //类似与run方法的实现 Callable是一个接口,在call中手写逻辑代码
        Callable<Integer> callable = new Callable<Integer>() {
            public Integer call() throws Exception {
                Integer res = new Random().nextInt(100);
                Thread.sleep(1000);
                System.out.println("任务执行:获取到结果 :"+res);
                return  res;
            }
        };

        for(int i=0;i<10;i++){
            //创建一个异步任务
            FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
            futureTasks.add(futureTask);
            //提交异步任务到线程池,让线程池管理任务 特爽把。
            //由于是异步并行任务,所以这里并不会阻塞
            executorService.submit(futureTask);
        }

        int count = 0;
        for (FutureTask<Integer> futureTask : futureTasks) {
            //futureTask.get() 得到我们想要的结果
            //该方法有一个重载get(long timeout, TimeUnit unit) 第一个参数为最大等待时间,第二个为时间的单位
            count+= futureTask.get();
        }
        long end = System.currentTimeMillis();
        System.out.println("线程池的任务全部完成:结果为:"+count+",main线程关闭,进行线程的清理");
        System.out.println("使用时间:"+(end-start)+"ms");
        //清理线程池
        executorService.shutdown();
    }
}

上述情况如果不用异步并行,程序将至少睡眠10s。执行结果如下:

 

 

 

试着把线程池的大小减少一半,即 Executors.newFixedThreadPool(5),执行耗时增加了1倍。执行结果如下:

 

 

 

线程池工程原理

  多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。    
  假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。


  如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。
                一个线程池包括以下四个基本组成部分:
                1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
                2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
                3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
                4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
                
    线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。
    线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目,看一个例子:
    假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。
    代码实现中并没有实现任务接口,而是把Runnable对象加入到线程池管理器(ThreadPool),然后剩下的事情就由线程池管理器(ThreadPool)来完成了

 

posted @ 2022-06-16 11:49  IT民工郑小江  阅读(1024)  评论(0编辑  收藏  举报