zoukankan      html  css  js  c++  java
  • 通过Executors创建线程池和注意小点

    Executors提供的工厂方法有:

    FixedThreadPool(n):创建一个数量固定的线程池,超出的任务会在队列中等待空闲的线程,可用于控制程序的最大并发数。
    CachedThreadPool():短时间内处理大量工作的线程池,会根据任务数量产生对应的线程,并试图缓存线程以便重复使用,如果限制 60 秒没被使用,则会被移除缓存。
    SingleThreadExecutor():创建一个单线程线程池。
    ScheduledThreadPool(n):创建一个数量固定的线程池,支持执行定时性或周期性任务。
    SingleThreadScheduledExecutor():此线程池就是单线程的 newScheduledThreadPool。
    WorkStealingPool(n):Java 8 新增创建线程池的方法,创建时如果不设置任何参数,则以当前机器处理器个数作为线程个数,此线程池会并行处理任务,不能保证执行顺序。

    可以看到各种不同的工厂方法中使用的线程池实现类最终只有3个,对应关系如下:

    工厂方法实现类
    newCachedThreadPool ThreadPoolExecutor
    newFixedThreadPool ThreadPoolExecutor
    newSingleThreadExecutor ThreadPoolExecutor
    newScheduledThreadPool ScheduledThreadPoolExecutor
    newSingleThreadScheduledExecutor ScheduledThreadPoolExecutor
    newWorkStealingPool ForkJoinPool

    ThreadPoolExecutor VS Executors

    ThreadPoolExecutor 和 Executors 都是用来创建线程池的,其中 ThreadPoolExecutor 创建线程池的方式相对传统,而 Executors 提供了更多的线程池类型(6 种),但很不幸的消息是在实际开发中并不推荐使用 Executors 的方式来创建线程池。

    无独有偶《阿里巴巴 Java 开发手册》中对于线程池的创建也是这样规定的,内容如下:

    线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的读者更加明确线程池的运行规则,规避资源耗尽的风险。
    
    说明:Executors 返回的线程池对象的弊端如下:
    
    1)FixedThreadPool 和 SingleThreadPool:
    
    允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
    
    2)CachedThreadPool 和 ScheduledThreadPool:
    
    允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

    FixedThreadPool为例:

    public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
    }

    可以看到创建 FixedThreadPool 使用了 LinkedBlockingQueue 作为任务队列,继续查看 LinkedBlockingQueue 的源码就会发现问题的根源,源码如下:

    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    当使用 LinkedBlockingQueue 并没有给它指定长度的时候,默认长度为 Integer.MAX_VALUE,这样就会导致程序会给线程池队列添加超多个任务,因为任务量太大就有造成 OOM 的风险。

    上例子:

    实现了Runnable的类:

    public class ThreadRunnable implements Runnable {
        @Override
        public void run() {
    
            System.out.println(Thread.currentThread().getName() + ">>>>>>>CurrentTime:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    NewCachedThreadPool:

    public class NewCachedThreadPool {
        public static void main(String[] args) {
            //线程池为无限大,当执行当前任务时上一个任务已经完成,会复用执行上一个任务的线程,而不用每次新建线程
            ExecutorService pool = Executors.newCachedThreadPool();
            ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) pool;
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                threadPoolExecutor.execute(new ThreadRunnable());
    //            threadPoolExecutor.execute(() -> {
    //                System.out.println(Thread.currentThread().getName() + ">>>>>>>CurrentTime:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    //                 try {
    //                    Thread.sleep(1000);
    //                } catch (InterruptedException e) {
    //                    e.printStackTrace();
    //                }
    //            });
            }
            threadPoolExecutor.shutdown();
        }
    }

    结果:

     根据执行结果可以看出,newCachedThreadPool 在短时间内会创建多个线程来处理对应的任务,并试图把它们进行缓存以便重复使用。

    NewFixedThreadPool:

    public class NewFixedThreadPool {
        public static void main(String[] args) {
            //线程池数量指定为2代表就创建两个线程,无论多少消息就用两条线程去处理,
            // 如果线程数量为1,类似单线程执行方法
            //创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。
    //        ExecutorService pool = Executors.newFixedThreadPool(1);
            ExecutorService pool = Executors.newFixedThreadPool(2);
            ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) pool;
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                threadPoolExecutor.execute(new ThreadRunnable());
            }
            threadPoolExecutor.shutdown();
        }
    }

    结果:

     NewScheduledThreadPool:

    public class NewScheduledThreadPool {
        //创建一个定长线程池,支持定时及周期性任务执行
        public static void main(String[] args) {
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println("线程:"+Thread.currentThread().getName()+">>>延迟1秒后每3秒执行一次,时间是:"+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss")));
                }
            }, 1000, 3000, TimeUnit.MILLISECONDS);
    //        不能有关闭方法,否则线程池关闭就无法持久输出了
    //        scheduledExecutorService.shutdown();
        }
    }

    结果:

     一直持续下去。。。。。。

    NewSingleThreadExecutor:

    public class NewSingleThreadExecutor {
    static int a = 0;
    //创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
    public static void main(String[] args) {
    ExecutorService pool = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    pool.execute(() -> {
    ++a;
    System.out.println(Thread.currentThread().getName() + "顺序打印了" + a);
    });
    }
    pool.shutdown();
    }
    }

    结果:

    NewSingleThreadScheduledExecutor:

    public class NewSingleThreadScheduledExecutor {
    //创建一个可以执行周期性任务的单线程池,具体代码如下:
        public static void main(String[] args) {
            ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
            scheduledExecutorService.schedule(() -> {
                System.out.println("ThreadPool:" + LocalDateTime.now());
            }, 3L, TimeUnit.SECONDS);
            System.out.println("CurrentTime:" + LocalDateTime.now());
        }
    }

    结果:

     根据执行结果可以看出,我们设置的 3 秒后执行的任务生效了。

    NewWorkStealingPool:

    public class NewWorkStealingPool {
        //Java 8 新增的创建线程池的方式,可根据当前电脑 CPU 处理器数量生成相应个数的线程池,使用代码如下:
        public static void main(String[] args) {
            ExecutorService pool = Executors.newWorkStealingPool();
            for (int i = 0; i < 30; i++) {
                pool.execute(() -> {
                    System.out.println(Thread.currentThread().getName());
                });
            }
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    结果:

     根据执行结果可以看出,newWorkStealingPool 是并行处理任务的,并不能保证执行顺序。

    ExecutorService 的submit() 与execute()区别

    1、接收的参数不一样 submit()可以接受runnable无返回值和callable有返回值
    execute()接受runnable 无返回值

    2、submit有返回值,而execute没有

    Method submit extends base method Executor.execute by creating and returning a Future that can be used to cancel execution and/or wait for completion.

    用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,然后每个task告诉我它的执行结果,是成功还是失败,如果是失败,原因是什么。

    3、submit方便Exception处理

    There is a difference when looking at exception handling. If your tasks throws an exception and if it was submitted with execute this exception will go to the uncaught exception handler (when you don’t have provided one explicitly, the default one will just print the stack trace to System.err). If you submitted the task with submit any thrown exception, checked or not, is then part of the task’s return status. For a task that was submitted with submit and that terminates with an exception, the Future.get will rethrow this exception, wrapped in an ExecutionException.

    意思就是如果你在你的task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。

    shotdown() showdownNow()区别

    可以关闭 ExecutorService,这将导致其拒绝新任务。提供两个方法来关闭 ExecutorService。
    shutdown() 方法在终止前允许执行以前提交的任务,
    shutdownNow() 方法阻止等待任务启动并试图停止当前正在执行的任务。在终止时执行程序没有任务在执行,也没有任务在等待执行,并且无法提交新任务。关闭未使用的 ExecutorService 以允许回收其资源。
    一般分两个阶段关闭 ExecutorService。第一阶段调用 shutdown 拒绝传入任务,然后调用 shutdownNow(如有必要)取消所有遗留的任务

    // 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
    threadPool.shutdown();

    Runnable()与Callable()区别

    如果是一个多线程协作程序,比如菲波拉切数列,1,1,2,3,5,8…使用多线程来计算。
    但后者需要前者的结果,就需要用callable接口了。
    callable用法和runnable一样,只不过调用的是call方法,该方法有一个泛型返回值类型,你可以任意指定。

    runnable接口实现的没有返回值的并发编程。
    callable实现的存在返回值的并发编程。(call的返回值String受泛型的影响) 使用Future获取返回值。

    代码实现:

    class test4 implements Callable{
    
        @Override
        public Object call() throws Exception {
            //System.out.println("科技");
            return "1";
        }
    }
    
    class test5{
        static int a = 0;
        public static void main(String[] args) {
            ExecutorService pool = Executors.newCachedThreadPool();
            ArrayList<Future<String>> list = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
    //            pool.submit(new Runnable() {
    //                @Override
    //                public void run() {
    //                    System.out.println("我打印了"+ (a++));
    //                }
    //            });
                Future<String> submit = pool.submit(new test4());
                list.add(submit);
            }
            pool.shutdown();
            System.out.println(list.size());
            // 遍历任务的结果
            for (Future<String> fs : list) {
                try {
                    System.out.println(fs.get()); // 打印各个线程(任务)执行的结果
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }
    }

     小结:

    总结
    Executors 可以创建 6 种不同类型的线程池,其中

    newFixedThreadPool() 适合执行单位时间内固定的任务数,

    newCachedThreadPool() 适合短时间内处理大量任务,

    newSingleThreadExecutor() 和 newSingleThreadScheduledExecutor() 为单线程线程池,而 newSingleThreadScheduledExecutor() 可以执行周期性的任务,是 newScheduledThreadPool(n) 的单线程版本,

    而 newWorkStealingPool() 为 JDK 8 新增的并发线程池,可以根据当前电脑的 CPU 处理数量生成对比数量的线程池,但它的执行为并发执行不能保证任务的执行顺序。

     该贴可以看一下:

    https://www.cnblogs.com/ants/p/11343657.html

  • 相关阅读:
    socket-重叠模型(overlap)
    ssh 免密登陆
    安装google 框架
    为什么不同网段的ip 不能直接通信
    python中的import,reload,以及__import__
    C Runtime Library、C  Runtime
    SQLite3 C/C++ 开发接口简介
    mysql添加索引语句
    mysql 字段左右补0
    @Transactional注解的失效场景
  • 原文地址:https://www.cnblogs.com/lzghyh/p/12650633.html
Copyright © 2011-2022 走看看