线程池实战

2020-10-28  本文已影响0人  java小杰要加油

更多精彩请关注公众号xhJaver,京东java工程师和你一起成长

上一篇我们讲了讲线程池的基本概念以及几种常见的线程池,今天我们来趁热打铁模拟下在项目中怎么用这线程池

一、线程池实战例子

项目背景:需要查出一百个用户的信息,并且给他们的邮箱发送邮件,打印出最终结果

用户类

public class User {
    private Integer id;
    private String email;

    public User(Integer id, String email) {
        this.id =id;
        this.email =email;
    }

    public String getEmail() {
        return email;
    }
}

任务类

public class Task implements Callable<String> {
   private Integer id;
   
    public Task(Integer id) {
        this.id = id;
    }

    @Override
    public String call() throws Exception {
        //调用业务方提供的查user的服务,id不同,创建任务的时候就传过来id
        User user = DoSomethingService.queryUser(this.id);
        //调用业务方提供发送邮件的服务,email不同
        String result = DoSomethingService.sendUserEmail(user.getEmail());
        return result;
    }
}

提供的服务类

//业务提供的服务
public  class DoSomethingService {
    //查询用户100ms
     public static  User queryUser(Integer id) throws InterruptedException {
         //这里可以调用查询user的sql语句
         Thread.sleep(100);
         User u= new User(id,id+"xhJaver.com");
         return u;
     }

     //发送邮件50ms
    public static String sendUserEmail(String email) throws InterruptedException {
        if (email!=null){
            //这里可以调用发送email的语句
            Thread.sleep(50);
            return "发送成功"+email;
        }else {
            return "发送失败";
        }

    }
}

我们再来比较一下单线程情况下和多线程情况下相同的操作差别有多大

public class SingleVSConcurrent {
    public static void main(String[] args) {
     //我们模拟一百个用户,我们查出来这一百个用户然后再给他们发邮件
        long singleStart = System.currentTimeMillis();
        for (int i=0;i<100;i++){
            User user = null;
            try {
                user = DoSomethingService.queryUser(i);
                String s = DoSomethingService.sendUserEmail(user.getEmail());
                System.out.println(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        long singleEnd = System.currentTimeMillis();
        System.out.println("单线程共用了"+(singleEnd-singleStart)+"ms");
        System.out.println("-------分割线-----------------分割线-----------------分割线-----------------分割线-----------------分割线----------");
        long concurrentStart = System.currentTimeMillis();
        //构建要做的任务列表,查询出用户来并且发送邮件
        List<Task> tasks = new ArrayList<>();
        for (int i=0;i<100;i++){
            //传id进去构造不同的任务,业务中有可能是给你个list列表
            Task task = new Task(i);
            tasks.add(task);
        }
        //返回任务执行结果
        List<Future<String>> futures = null;
         //用线程池查询用户发送邮件
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        try {
            //是线程池执行提交的批量任务
            futures = executorService.invokeAll(tasks);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //关闭线程池
        executorService.shutdown();

        //存放任务结果的集合
        List<String> results = new ArrayList<>();
        //遍历这个任务执行结果
        for (Future<String> result:futures) {
            //如果这个任务结束了
            if (result.isDone()){
                String s = null;
                try {
                    //得到这个任务的处理结果,得不到会一直阻塞
                    s = result.get();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //将任务结果放进任务结果集合里面
                results.add(s);
            }
        }
        //遍历任务结果的集合
        for (String s:results) {
            System.out.println(s);
        }
        long concurrentEnd = System.currentTimeMillis();
        System.out.println("多线程共用了"+(concurrentEnd-concurrentStart)+"ms");
    }
}

我们最终会看到输出结果为

发送成功0xhJaver.com
发送成功1xhJaver.com
发送成功2xhJaver.com
发送成功3xhJaver.com
发送成功4xhJaver.com
发送成功5xhJaver.com
发送成功6xhJaver.com
发送成功7xhJaver.com
发送成功8xhJaver.com
发送成功9xhJaver.com
发送成功10xhJaver.com
发送成功11xhJaver.com
发送成功12xhJaver.com
发送成功13xhJaver.com
发送成功14xhJaver.com
发送成功15xhJaver.com
发送成功16xhJaver.com
发送成功17xhJaver.com
发送成功18xhJaver.com
发送成功19xhJaver.com
发送成功20xhJaver.com
发送成功21xhJaver.com
发送成功22xhJaver.com
发送成功23xhJaver.com
发送成功24xhJaver.com
发送成功25xhJaver.com
发送成功26xhJaver.com
发送成功27xhJaver.com
发送成功28xhJaver.com
发送成功29xhJaver.com
发送成功30xhJaver.com
发送成功31xhJaver.com
发送成功32xhJaver.com
发送成功33xhJaver.com
发送成功34xhJaver.com
发送成功35xhJaver.com
发送成功36xhJaver.com
发送成功37xhJaver.com
发送成功38xhJaver.com
发送成功39xhJaver.com
发送成功40xhJaver.com
发送成功41xhJaver.com
发送成功42xhJaver.com
发送成功43xhJaver.com
发送成功44xhJaver.com
发送成功45xhJaver.com
发送成功46xhJaver.com
发送成功47xhJaver.com
发送成功48xhJaver.com
发送成功49xhJaver.com
发送成功50xhJaver.com
发送成功51xhJaver.com
发送成功52xhJaver.com
发送成功53xhJaver.com
发送成功54xhJaver.com
发送成功55xhJaver.com
发送成功56xhJaver.com
发送成功57xhJaver.com
发送成功58xhJaver.com
发送成功59xhJaver.com
发送成功60xhJaver.com
发送成功61xhJaver.com
发送成功62xhJaver.com
发送成功63xhJaver.com
发送成功64xhJaver.com
发送成功65xhJaver.com
发送成功66xhJaver.com
发送成功67xhJaver.com
发送成功68xhJaver.com
发送成功69xhJaver.com
发送成功70xhJaver.com
发送成功71xhJaver.com
发送成功72xhJaver.com
发送成功73xhJaver.com
发送成功74xhJaver.com
发送成功75xhJaver.com
发送成功76xhJaver.com
发送成功77xhJaver.com
发送成功78xhJaver.com
发送成功79xhJaver.com
发送成功80xhJaver.com
发送成功81xhJaver.com
发送成功82xhJaver.com
发送成功83xhJaver.com
发送成功84xhJaver.com
发送成功85xhJaver.com
发送成功86xhJaver.com
发送成功87xhJaver.com
发送成功88xhJaver.com
发送成功89xhJaver.com
发送成功90xhJaver.com
发送成功91xhJaver.com
发送成功92xhJaver.com
发送成功93xhJaver.com
发送成功94xhJaver.com
发送成功95xhJaver.com
发送成功96xhJaver.com
发送成功97xhJaver.com
发送成功98xhJaver.com
发送成功99xhJaver.com
单线程共用了18404ms
-------分割线-----------------分割线-----------------分割线-----------------分割线-----------------分割线----------
发送成功0xhJaver.com
发送成功1xhJaver.com
发送成功2xhJaver.com
发送成功3xhJaver.com
发送成功4xhJaver.com
发送成功5xhJaver.com
发送成功6xhJaver.com
发送成功7xhJaver.com
发送成功8xhJaver.com
发送成功9xhJaver.com
发送成功10xhJaver.com
发送成功11xhJaver.com
发送成功12xhJaver.com
发送成功13xhJaver.com
发送成功14xhJaver.com
发送成功15xhJaver.com
发送成功16xhJaver.com
发送成功17xhJaver.com
发送成功18xhJaver.com
发送成功19xhJaver.com
发送成功20xhJaver.com
发送成功21xhJaver.com
发送成功22xhJaver.com
发送成功23xhJaver.com
发送成功24xhJaver.com
发送成功25xhJaver.com
发送成功26xhJaver.com
发送成功27xhJaver.com
发送成功28xhJaver.com
发送成功29xhJaver.com
发送成功30xhJaver.com
发送成功31xhJaver.com
发送成功32xhJaver.com
发送成功33xhJaver.com
发送成功34xhJaver.com
发送成功35xhJaver.com
发送成功36xhJaver.com
发送成功37xhJaver.com
发送成功38xhJaver.com
发送成功39xhJaver.com
发送成功40xhJaver.com
发送成功41xhJaver.com
发送成功42xhJaver.com
发送成功43xhJaver.com
发送成功44xhJaver.com
发送成功45xhJaver.com
发送成功46xhJaver.com
发送成功47xhJaver.com
发送成功48xhJaver.com
发送成功49xhJaver.com
发送成功50xhJaver.com
发送成功51xhJaver.com
发送成功52xhJaver.com
发送成功53xhJaver.com
发送成功54xhJaver.com
发送成功55xhJaver.com
发送成功56xhJaver.com
发送成功57xhJaver.com
发送成功58xhJaver.com
发送成功59xhJaver.com
发送成功60xhJaver.com
发送成功61xhJaver.com
发送成功62xhJaver.com
发送成功63xhJaver.com
发送成功64xhJaver.com
发送成功65xhJaver.com
发送成功66xhJaver.com
发送成功67xhJaver.com
发送成功68xhJaver.com
发送成功69xhJaver.com
发送成功70xhJaver.com
发送成功71xhJaver.com
发送成功72xhJaver.com
发送成功73xhJaver.com
发送成功74xhJaver.com
发送成功75xhJaver.com
发送成功76xhJaver.com
发送成功77xhJaver.com
发送成功78xhJaver.com
发送成功79xhJaver.com
发送成功80xhJaver.com
发送成功81xhJaver.com
发送成功82xhJaver.com
发送成功83xhJaver.com
发送成功84xhJaver.com
发送成功85xhJaver.com
发送成功86xhJaver.com
发送成功87xhJaver.com
发送成功88xhJaver.com
发送成功89xhJaver.com
发送成功90xhJaver.com
发送成功91xhJaver.com
发送成功92xhJaver.com
发送成功93xhJaver.com
发送成功94xhJaver.com
发送成功95xhJaver.com
发送成功96xhJaver.com
发送成功97xhJaver.com
发送成功98xhJaver.com
发送成功99xhJaver.com
多线程共用了233ms

从输出结果可以知道 单线程共用18404ms / 150 约等于122 多线程共用233ms /150 约等于1

就相当于发用查询发送一个人的时间解决了这100个人的问题,具体的线程池核心大小数量要根据业务方面自己配置设计

这里面出现了很多和上一篇不会吧,就是你把线程池讲的这么清楚的?没有讲到的知识点,感兴趣的可以去看一看

二、例子重点讲解

文中注释也都清晰明了的,我在解释下几个重要的

2.1.创建任务

自定义任务类实现这个接口并且实现call方法,返回值为传入Callable的类型即可

public class Task implements Callable<String> {
   private Integer id;
   
    public Task(Integer id) {
        this.id = id;
    }

    @Override
    public String call() throws Exception {
        //调用业务方提供的查user的服务,id不同,创建任务的时候就传过来id
        User user = DoSomethingService.queryUser(this.id);
        //调用业务方提供发送邮件的服务,email不同
        String result = DoSomethingService.sendUserEmail(user.getEmail());
        return result;
    }
}

2.2.构造的任务列表

//构建要做的任务列表,查询出用户来并且发送邮件
        List<Task> tasks = new ArrayList<>();
        for (int i=0;i<100;i++){
            //传id进去构造不同的任务,业务中有可能是给你个list列表
            Task task = new Task(i);
            tasks.add(task);
        }

2.3.创建线程池提交任务列表并且关闭线程池

 //用线程池查询用户发送邮件
        ExecutorService executorService = Executors.newFixedThreadPool(100);
  //返回任务执行结果
        List<Future<String>> futures = null;
        try {
            //是线程池执行提交的批量任务
            futures = executorService.invokeAll(tasks);
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
           //关闭线程池
        executorService.shutdown();

注:提交任务的时候也可以是submit不过在这样一次提交一个任务,要是有任务列表可以用invokeAll shoutdown和shoutdownNow都可以关闭线程池。但是又很大的区别 shoutdown :不会立刻停止线程池,但是会拒绝处理新来的任务,阻塞队列中的任务等他执行完 shoutdownNow:会立刻停止线程池,拒绝处理新来的任务并且打断正在执行的线程,将阻塞队列中的任务全部清空 总的来说就是shoutdown温柔一点,shoutdownNow粗暴一点

2.3.1线程池提交callable任务四种方法讲解

关于提交线程池提交callable任务有以下四种方法

  <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

如果调没有异常发生的话都会调用成功 如果有一个有异常则有异常的调用失败,其余成功

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

当全部任务执行完后超过指定时限后,直接抛出异常

    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

invokeAny将第一个完成的作为结果,或者调用失败则也立即终止其他所有线程。

 <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;

若设置了超时时间,未超时完成则返回正常结果,否则报错

2.4.解析任务结果

//存放任务结果的集合
        List<String> results = new ArrayList<>();
        //遍历这个任务执行结果
        for (Future<String> result:futures) {
            //如果这个任务结束了
            if (result.isDone()){
                String s = null;
                try {
                    //得到这个任务的处理结果,得不到会一直阻塞
                    s = result.get();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //将任务结果放进任务结果集合里面
                results.add(s);
            }
        } 
        //遍历任务结果的集合
        for (String s:results) {
            System.out.println(s);
        }

三、future接口方法讲解

注:future是个jdk1.5以后出现的异步任务接口

public interface Future<V> {
   
   //若此任务已经完成或者被取消或者因为其他原因不能被取消则取消失败,
   //如果此任务还没有开始或者已经开始这个时候就由这个参数来觉得取消与否
  // 在这个方法执行完后,isDone,iscancel总是返回true,
  //如果任务无法取消经常是因为任务被执行完了
    boolean cancel(boolean mayInterruptIfRunning);

  //是否取消成功
    boolean isCancelled();

   //任务是否完成,正常结束取消或者异常,都返回true
    boolean isDone();

  //得到这个任务的结果,会一直阻塞到得到结果
  //如果任务被取消,则抛出CancellationException  异常
  //若任务有异常,则抛出ExecutionException  异常
  //若任务被中断则抛出InterruptedException 异常

    V get() throws InterruptedException, ExecutionException;
    
  //得到这个任务的结果,若超过时间会抛异常,除此之外和上面那个方法一样
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

我们文中的例子用到了 V get()这个,下一篇我打算写一写线程池为什么可以做到线程复用? 源码面前,没有秘密

更多精彩请关注公众号xhJaver,京东java工程师和你一起成长

上一篇下一篇

猜你喜欢

热点阅读