线程池实战

更多精彩请关注公众号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任务有以下四种方法

  • invokeAll无时间参数
  <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

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

  • invokeAll有时间参数
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

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

  • invokeAny无时间参数
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

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

  • 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工程师和你一起成长

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354