Java ExecutorService四种线程池的使用

一、Executors提供的四种线程

  1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

 二、下面对四种线程池的使用进行简单的代码实现

1. newCachedThreadPool() 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 

public static void newCachedThreadPoolTest() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 200; i++) {
            final int index = i;
            try {
                Thread.sleep(index);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.execute(() -> System.out.println(Thread.currentThread().getName() + "," + index));
        }
        //shutdown 的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
        //shutdownNow 会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
        executorService.shutdown();
    }

控制台输出:

注:介绍一下shutdown()和shutdownNow()区别:

线程池的关闭 我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,但是它们的实现原理不

  1. shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
  2. shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。

只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。

 2. newFixedThreadPool() 创建一个定长的线程池,可控制线程最大并发数,超出的线程会在队列中等待

public static void newFixedThreadPoolTest(){
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 200; i++) {
            final int index = i;
            fixedThreadPool.execute(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + "," + index);
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            // 以上代码可以改写成submit提交任务的lambda表达式的形式
            fixedThreadPool.submit(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + ", " + index);
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, true);
        }
    }

 注:这里介绍一下使用execute()和submit()方法提交任务的区别:

向线程池提交任务 ThreadPoolExecutor类中execute()和submit()区别

  • execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
  • submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,通过源码查看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。
//submit()源码
/**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

 3.  newScheduledThreadPool() 创建定长的线程池,支持周期和定时任务

// 示例如下:
    public static void newScheduledThreadPoolTest() {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        System.out.println("before:" + LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);
        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("延迟3秒执行一次:" + System.currentTimeMillis() / 1000);
            }
        }, 3, TimeUnit.SECONDS);
        System.out.println("after:" + LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);

    }

    System.out.println("----------------------------华丽的分割线-------------------------");
    System.out.println("begin:" + LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);
    // initialDelay:延迟首次执行的时间,period:连续执行之间的时间间隔,unit:initialDelay 和 period的时间单位
    scheduledThreadPool.scheduleAtFixedRate(() -> System.out.println("首次执行时间延迟1s,然后3s执行一次:" + System.currentTimeMillis() / 1000), 1, 3, TimeUnit.SECONDS);
    System.out.println("end:" + LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);

4. newSingleThreadExecutor() 创建一个单线程化的线程池,只会用工作线程来执行任务,保证顺序

// 示例如下:
    public static void newSingleThreadExecutorTest(){
        ExecutorService singleThread = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 100; i++) {
            final int index = i;
            singleThread.execute(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + "," + index);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }

三、 线程池分析 

1. 线程池的主要工作流程如下图:

从上图我们可知,当提交一个新任务到线程池时,线程池的处理流程如下: 

  1. 首先线程池判断核心线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
  2. 其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
  3. 最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。

2. 源码分析。

上面的流程分析让我们很直观的了解的线程池的工作原理,让我们再通过源代码来看看是如何实现的。线程池执行任务的方法如下: 

public void execute(Runnable command) {
  if (command == null)
    throw new NullPointerException();
  int c = ctl.get();
  if (workerCountOf(c) < corePoolSize) {
    if (addWorker(command, true))
      return;
    c = ctl.get();
  }
  if (isRunning(c) && workQueue.offer(command)) {
    int recheck = ctl.get();
    if (! isRunning(recheck) && remove(command))
      reject(command);
    else if (workerCountOf(recheck) == 0)
      addWorker(null, false);
  }
  else if (!addWorker(command, false))
    reject(command);
}

工作线程。线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会无限循环获取工作队列里的任务来执行。

参考文章链接:https://www.jb51.net/article/134862.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章