Ali Daniu collection of architectural materials, click the link for free access

In an in-depth Source Code Analysis of Java Thread Pool implementation principles, we covered common usage and fundamentals of thread pools in Java.

There is such a description in the article:

You can build a thread pool by using Executors Static Factory, but this is generally not recommended.

This question was not developed in depth in that article. Authors say so, because this approach to create a thread pool has a lot of hidden trouble, slightly has carelessly may lead to line fault, such as a Java thread pool a murder case caused by misuse and summary (zhuanlan.zhihu.com/p/32867181)

This article looks at why the JDK’s own approach to building thread pools is not recommended. How do you create a thread pool?

Executors

Executors is a Java tool class. Provides factory methods to create different types of thread pools.

As you can see from the figure above, the thread pool created by Executors implements the ExecutorService interface. The common methods are as follows:

NewFiexedThreadPool (int Threads) : Creates a thread pool with a fixed number of Threads.

NewCachedThreadPool () : Creates a cacheable thread pool, and calls to execute reuse previously constructed threads (if available). If no thread is available, a new thread is created and added to the pool. Terminates and removes threads from the cache that have not been used for 60 seconds.

NewSingleThreadExecutor () creates a single threaded Executor.

NewScheduledThreadPool (int corePoolSize) creates a thread pool that supports timed and periodic task execution and can be used in most cases in place of the Timer class.

Class looks powerful, uses the factory mode, and has a strong extensibility, importantly, is relatively convenient to use, such as:

ExecutorService executor = Executors.newFixedThreadPool(nThreads) ;Copy the code

You can create a thread pool of fixed size.

But why do I recommend not using this class to create thread pools?

I said “do not recommend”, but it’s also specified in the Alibaba Java Development Manual, and uses the phrase “Do not allow” to create thread pools using Executors.

What is the problem with Executors

* If you create a thread pool by using Executors, you may cause OOM(OutOfMemory). * If you create a thread pool by using Executors, you may cause OOM(OutOfMemory).

Let’s start off with a quick example that mimics the effects of using Executors.

/**
 * @author Hollis
 */
public class ExecutorsDemo {
    private static ExecutorService executor = Executors.newFixedThreadPool(15);
    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executor.execute(new SubThread());
        }
    }
}

class SubThread implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            //do nothing
        }
    }
}Copy the code

Run the above code by specifying the JVM argument -xmx8m -xMS8m to raise OOM:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
    at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)Copy the code

Executor.execute (new SubThread())) is line 16 of executorsDemo.java; .

Why does Executors Have defects

If the thread pool created by Executors is at risk of OOM, so what is the cause? We need to dig into the source of Executors to analyze it.

In fact, in the error message above, we can see the clues, actually already said in the above code, the real cause of OOM is LinkedBlockingQueue. Offer method.

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
    at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)Copy the code

If you look at the code, you can see that the underlying layer is actually implemented via LinkedBlockingQueue:

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

If you’re familiar with blocking queues in Java, you’ll see why.

There are two main implementations of BlockingQueue in Java, ArrayBlockingQueue and LinkedBlockingQueue.

ArrayBlockingQueue is a bounded blocking queue implemented as an array that must be sized.

LinkedBlockingQueue is a LinkedBlockingQueue with an optional size, otherwise it will be an unbounded queue with a maximum length of integer.max_value.

The problem here is that if it is not set, it will be a borderless blocking queue with a maximum length of integer.max_value. That is, if we don’t set the size of LinkedBlockingQueue, the default size will be integer.max_value.

When LinkedBlockingQueue is created in newFixedThreadPool, the size is not specified. In this case, LinkedBlockingQueue is an unbounded queue, and an unbounded queue can continually add tasks to the queue. In this case, it is possible to run out of memory due to too many tasks.

NewFixedThreadPool and newSingleThreadExecutor are the main problems mentioned above. This does not mean that newCachedThreadPool and newScheduledThreadPool are safe. The maximum number of threads can be integer. MAX_VALUE, which will inevitably result in OOM.

The correct posture for creating a thread pool

To avoid using Executors or the default implementation, create your own thread pool by calling the ThreadPoolExecutor constructor. At the same time as the BlockQueue is created, you can specify the capacity.

private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue(10));Copy the code

This case, once submitted to the number of threads than currently available threads, Java will be thrown. Util. Concurrent. RejectedExecutionException, this is because the current thread pool using border queue, queue is a queue is full cannot continue to process new requests. But an Exception is better than an Error.

In addition to defining your own ThreadPoolExecutor. There are other ways. Open source libraries such as Apache and Guava are the first to come to mind.

The authors recommend using The ThreadFactoryBuilder provided by Guava to create thread pools.

public class ExecutorsDemo { private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); private static ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); public static void main(String[] args) { for (int i = 0; i < Integer.MAX_VALUE; i++) { pool.execute(new SubThread()); }}}Copy the code

When creating threads in the above way, you can not only avoid OOM problems, but also customize the thread name, which is more convenient to trace to the source when errors occur.

It is better to have an Exception than an Error.

The “Alibaba Java Development Manual” mentioned in the article, please pay attention to the public account Hollis, reply: manual. The full PDF is available.