Four commonly used Java thread pool

table of Contents

1 Introduction

2、newCachedThreadPool

2.1 Example

2.2 Detailed

2.3 CachedThreadPool the execute () execution

3、newFixedThreadPool

3.1 Example

3.2 Detailed

3.3 FixedThreadPool execution of the execute () method

4、newScheduledThreadPool

4.1 Example

4.2 Detailed

4.3 ScheduledThreadPoolExecutor operating mechanism

4.4 ScheduledThreadPoolExecutor implementation

5 newSingleThreadExecutor

5.1 Example

5.2 Detailed

6, attention

7, reference


1 Introduction

Java provides four thread pool by Executors, respectively:

  • newCachedThreadPool create a cache thread pool, thread pool longer than if treatment needs, the flexibility to reclaim idle thread, if not recyclable, the new thread.
  • newFixedThreadPool create a fixed-size thread pool, you can control the maximum number of concurrent threads, excess threads will wait in the queue.
  • newScheduledThreadPool create a fixed-size thread pool to support regular and periodic task execution.
  • newSingleThreadExecutor create a single-threaded thread pool, use it only to perform the task only worker threads to ensure that all tasks are performed in a specified order (FIFO, LIFO, priorities).

2、newCachedThreadPool

2.1 Example

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        for (int i = 0; i < 5; i++) {
            final int index = i;

            try {
                Thread.sleep(index * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(index);
                }
            });
        }
    }
}

Thread pool is infinite, the first task has been completed when the second task, the thread used to perform the first task will be complex, rather than each time a new thread.

2.2 Detailed

CachedThreadPool will create a new thread pool threads as needed.

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

The CachedThreadPool corePoolSize is set to 0, i.e. corePool empty; maximumPoolSize Integer.MAX_VALUE is set, i.e. maximumPool is unbounded. Here the keepAliveTime set to 60L, meaning CachedThreadPool idle threads waiting for new tasks the maximum time is 60 seconds of idle threads will be terminated after more than 60 seconds. CachedThreadPool no capacity SynchronousQueue use as a work queue thread pool, but CachedThreadPool of maximumPool is unbounded. This means that if the speed of the main thread submit jobs is higher than the speed maximumPool thread processing tasks, CachedThreadPool will continue to create a new thread. In extreme cases, CachedThreadPool because of too many threads to create and run out of CPU and memory resources.

2.3 CachedThreadPool the execute () execution

1) First, the implementation of SynchronousQueue.offer (Runnable task). If there is space in the current maximumPool threads are executing SynchronousQueue.poll (keepAliveTime, TimeUnit.NANOSECONDS), then the main thread to perform operations and offer free poll operation performed successfully paired thread, the main thread of the task to idle thread execution, execute () method execution is completed; otherwise, executing step 2 below).

2) when the initial maximumPool empty, or maximumPool not currently idle threads, the thread performs no SynchronousQueue.poll (keepAliveTime, TimeUnit.NANOSECONDS). In this case, Step 1) will fail. At this point CachedThreadPool creates a new thread to perform tasks, execute () method execution is complete.

3) threads in Step 2) the newly created task will be executed, performs

SynchronousQueue.poll (keepAliveTime, TimeUnit.NANOSECONDS). The poll operation will wait up to 60 seconds of idle threads in the SynchronousQueue. If within 60 seconds the main thread has submitted a new task (the main thread steps 1)), then the idle thread will perform a new task submitted by the main thread; otherwise, this idle thread will terminate. Due to idle 60 seconds of idle threads are terminated, so long remain idle CachedThreadPool not use any resources.

As mentioned earlier, SynchronousQueue capacity is not a blocked queue. Each insert corresponds to the removal operation must wait another thread, or vice versa. CachedThreadPool use SynchronousQueue, the main thread to pass the task submitted to the idle thread.

3、newFixedThreadPool

3.1 Example

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 5; i++) {
            final int index = i;

            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            });
        }
    }
}

3.2 Detailed

FixedThreadPool referred reusable fixed number of threads the thread pool.

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

FixedThreadPool of corePoolSize and maximumPoolSize are specified for the parameters when creating FixedThreadPool nThreads.

When the number of threads in the thread pool greater than corePoolSize, keepAliveTime as excess idle threads waiting for new tasks longest time, excess threads will be terminated after more than this time. Here the keepAliveTime set to 0L, means that excess idle threads will be terminated immediately.

3.3 FixedThreadPool execution of the execute () method

  • 1) If the number of threads currently running less than corePoolSize, creating a new thread to execute the task.
  • 2) After the completion of warm-up thread pool (equal to the number of threads currently running corePoolSize), will join the task LinkedBlockingQueue.
  • 3) After the task is finished executing thread 1, to perform tasks repeatedly get from LinkedBlockingQueue in a loop.

note:

FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE)。使用无界队列作为工作队列会对线程池带来如下影响。

  • 1)当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize。
  • 2)由于1,使用无界队列时maximumPoolSize将是一个无效参数。
  • 3)由于1和2,使用无界队列时keepAliveTime将是一个无效参数。
  • 4)由于使用无界队列,运行中的 FixedThreadPool(未执行方法shutdown()或shutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution方法)。

4、newScheduledThreadPool

4.1 示例

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        // 延迟三秒执行
        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("delay 3 seconds");
            }
        }, 3, TimeUnit.SECONDS);
    }
}

4.2 详解

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor的功能与Timer类似,但ScheduledThreadPoolExecutor功能更强大、更灵活。Timer对应的是单个后台线程,而ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。

4.3 ScheduledThreadPoolExecutor的运行机制

ScheduledThreadPoolExecutor的执行示意图

ScheduledThreadPoolExecutor为了实现周期性的执行任务,对ThreadPoolExecutor做了如下的修改:

  • 使用DelayQueue作为任务队列。
  • 获取任务的方式不同(后文会说明)。
  • 执行周期任务后,增加了额外的处理(后文会说明)。

4.4 ScheduledThreadPoolExecutor的实现

ScheduledThreadPoolExecutor会把待调度的任务(ScheduledFutureTask)放到一个DelayQueue中。

ScheduledFutureTask主要包含3个成员变量,如下。

  • long型成员变量time,表示这个任务将要被执行的具体时间。
  • long型成员变量sequenceNumber,表示这个任务被添加到ScheduledThreadPoolExecutor中的序号。
  • long型成员变量period,表示任务执行的间隔周期。

DelayQueue封装了一个PriorityQueue,这个PriorityQueue会对队列中的Scheduled-FutureTask进行排序。排序时,time小的排在前面(时间早的任务将被先执行)。如果两个 ScheduledFutureTask的time相同,就比较sequenceNumber,sequenceNumber小的排在前面(也就是说,如果两个任务的执行时间相同,那么先提交的任务将被先执行)。

首先,让我们看看ScheduledThreadPoolExecutor中的线程执行周期任务的过程。

  • 1)线程1从DelayQueue中获取已到期的ScheduledFutureTask(DelayQueue.take())。到期任务是指ScheduledFutureTask的time大于等于当前时间。
  • 2)线程1执行这个ScheduledFutureTask。
  • 3)线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间。
  • 4)线程1把这个修改time之后的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。

 

接下来,让我们看看上面的步骤1)获取任务的过程。下面是DelayQueue.take()方法的源代码

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();             // 1
    try {
        for (;;) {
            E first = q.peek();
            if (first == null) {
                available.await();           // 2.1
            } else {
                long delay = first.getDelay(TimeUnit.NANOSECONDS);
                if (delay > 0) {
                    long tl = available.awaitNanos(delay);   // 2.2
                } else {
                    E x = q.poll();           // 2.3.1
                    assert x != null;
                    if (q.size() != 0)
                        available.signalAll();         // 2.3.2
                    return x;
                }
            }
        }
    } finally {
        lock.unlock();               // 3
    }
}

DelayQueue.take()的执行示意图

1)获取Lock。

2)获取周期任务。

2.1)如果PriorityQueue为空,当前线程到Condition中等待;否则执行下面的2.2。

2.2)如果PriorityQueue的头元素的time时间比当前时间大,到Condition中等待到time时间;否则执行下面的2.3。

2.3)获取PriorityQueue的头元素(2.3.1);如果PriorityQueue不为空,则唤醒在Condition中等待的所有线程(2.3.2)。

3)释放Lock。

ScheduledThreadPoolExecutor在一个循环中执行步骤2,直到线程从PriorityQueue获取到一个元素之后(执行2.3.1之后),才会退出无限循环(结束步骤2)。

最后,让我们看看ScheduledThreadPoolExecutor中的线程执行任务的步骤4,把ScheduledFutureTask放入DelayQueue中的过程。下面是DelayQueue.add()的源代码实现。

    public boolean add(E e) {
        return offer(e);
    }
    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.offer(e);
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

DelayQueue.add()的执行示意图:

  • 1)获取Lock。
  • 2)添加任务。
  • 2.1) 向PriorityQueue添加任务。
  • 2.2) 如果在上面2.1中添加的任务是PriorityQueue的头元素,唤醒在Condition中等待的所有线程。
  • 3)释放Lock。

5、newSingleThreadExecutor

5.1 示例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(index);
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

5.2 详解

SingleThreadExecutor是使用单个worker线程的Executor。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1。其他参数与FixedThreadPool相同。SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE)。SingleThreadExecutor使用无界队列作为工作队列对线程池带来的影响与FixedThreadPool相同.

SingleThreadExecutor的运行示意图

  • 1)如果当前运行的线程数少于corePoolSize(即线程池中无运行的线程),则创建一个新线程来执行任务。
  • 2)在线程池完成预热之后(当前线程池中有一个运行的线程),将任务加入LinkedBlockingQueue。
  • 3)线程执行完1中的任务后,会在一个无限循环中反复从LinkedBlockingQueue获取任务来执行。

6、注意

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

---Java 开发手册

可以使用google的一个java开发包,来处理线程池创建的问题,还可以给线程设置名字。

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>28.1-jre</version>
        </dependency>

eg:

用
ThreadFactory namedThreadFactory = 
    new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build();
ExecutorService fixedThreadPool = 
    new ThreadPoolExecutor(10,20,200L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),namedThreadFactory);

替代
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

7、参考

《Java开发手册》

《Java并发编程的艺术》

发布了95 篇原创文章 · 获赞 16 · 访问量 5万+

Guess you like

Origin blog.csdn.net/tiankong_12345/article/details/100532220
Recommended