Thread processing two or three things

Keywords: Java jvm JDK shell

[mandatory] thread pools are not allowed to be created by Executors, but by ThreadPoolExecutor. Such a processing method enables the students who write to be more clear about the running rules of thread pools and avoid the risk of resource exhaustion. The above is the mandatory provision on concurrency in the java Development Manual of Alibaba. Why do you make this provision? Here is an extension of my personal puzzle solving and making. This is the beginning of my personal learning notes.

I. why create manually:
1. Single thread pool

Executor executor = Executors.newSingleThreadExecutor();

2. Scalable thread pool

Executor executor1 = Executors.newCachedThreadPool();

3. Fixed length route pool

Executor executor2 = Executors.newFixedThreadPool(10);

The working principle of thread pool is as follows:

The above are three common thread pools. See the creation source code below:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

All are related to ThreadPoolExecutor. ThreadPoolExecutor constructor:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

When using Executor to create thread pool, there will be resource exhaustion or OOM error. The main problem is the problem of blocking queue, that is, workQueue.
1. See the source code of the new single thread executor creation. The number of core threads and the maximum number of threads in the thread pool are both 1. All the remaining redundant thread tasks are plugged into linkedblockingqueue < runnable > (). The following is the source code of linkedblockingqueue < runnable > () parameterless constructor,

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

Integer.MAX_VALUE=2147483647. According to the working principle of thread pool, if the thread pool is created automatically, it means that the length of LinkedBlockingQueue is not limited. This blocking queue may burst the thread pool in the case of high concurrency.
2. See the source code of newFixedThreadPool creation, the same as 1.
3. Look at the source code of newCachedThreadPool. This thread pool allows the creation of up to integer.max'value threads. The work queue is synchronousque. The following is the synchronousque creation source code:

    /**
     * Creates a {@code SynchronousQueue} with nonfair access policy.
     */
    public SynchronousQueue() {
        this(false);
    }

    /**
     * Creates a {@code SynchronousQueue} with the specified fairness policy.
     *
     * @param fair if true, waiting threads contend in FIFO order for
     *        access; otherwise the order is unspecified.
     */
    public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
    }

Look at the notes. SynchronousQueue is an unfair stack here. The problem of SynchronousQueue is not studied in detail here. In the future, we will specially learn to block queues in jdk.
The size of the newCachedThreadPool thread pool is controlled by the maximum number of threads in the thread pool. The automatically created thread pool is likely to create a lot of thread burst memory.
The above simple from the source point of view to answer the question.
The automatic creation of thread pool is to directly use Executors to create it. The disadvantages of the above are simple analysis. But looking at the source code, manual creation of thread pool is to directly create a new thread pool executor without a layer of shell, which is essentially the same.

2. ThreadFactory
Looking at the constructor source code of ThreadPoolExecutor above, I found ThreadFactory. ~ ~ ~
We create a task and deliver it to the Executor. The Executor will help us create a thread to execute the task. ThreadFactory is a factory that creates threads.
Look at the source code first.

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

/**
     * The default thread factory
     */
    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

ThreadFactory is an interface, and DefaultThreadFactory is the implementation of this interface in the JUC package.
First of all, let's take a look at the newThread method in this factory. The input parameter implements the Runnable interface. There is a Thread constructor in the method. There are two if judgments. By default, it is a non Guardian Thread, and the Thread priority is 5. The name of the Thread is the data accumulation of 1. The Thread group is determined according to whether the jvm has opened the Security Manager (the jvm is closed by default). If we don't define a ThreadFactory, the DefaultThreadFactory is used to create threads whether we use the automatic creation of Thread pool Executor or or manually create Thread pool ThreadPoolExecutor.
Is there any difference between manual thread creation and automatic thread creation? The most cursory and simple difference is that the thread names are different. The thread pools for different businesses need different names. In this case, the logs will be much cleaner when the complex businesses need to use the thread pools. In the actual development, this advantage is very important.
guava provides a ThreadFactoryBuilder to help us implement our own ThreadFactory.
Source code of core functions in ThreadFactoryBuilder

private static ThreadFactory build(ThreadFactoryBuilder builder) {
        final String nameFormat = builder.nameFormat;
        final Boolean daemon = builder.daemon;
        final Integer priority = builder.priority;
        final UncaughtExceptionHandler uncaughtExceptionHandler = builder.uncaughtExceptionHandler;
        final ThreadFactory backingThreadFactory = builder.backingThreadFactory != null ? builder.backingThreadFactory : Executors.defaultThreadFactory();
        final AtomicLong count = nameFormat != null ? new AtomicLong(0L) : null;
        return new ThreadFactory() {
            public Thread newThread(Runnable runnable) {
                Thread thread = backingThreadFactory.newThread(runnable);
                if (nameFormat != null) {
                    thread.setName(ThreadFactoryBuilder.format(nameFormat, count.getAndIncrement()));
                }

                if (daemon != null) {
                    thread.setDaemon(daemon);
                }

                if (priority != null) {
                    thread.setPriority(priority);
                }

                if (uncaughtExceptionHandler != null) {
                    thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
                }

                return thread;
            }
        };
    }

In the build design mode, we can define the thread name, whether it is a guardian thread, thread priority, and thread exception handler by ourselves. If you want to choose Custom ThreadFactory, ThreadFactoryBuilder is highly recommended.

III. submit and execute
There are two ways to submit task s to the thread pool
1.submit 2.execute
demo:

public static void testSubmitAndExecute() throws ExecutionException, InterruptedException {
        ThreadFactoryBuilder orderThreadFactoryBuilder = new ThreadFactoryBuilder();
        ThreadFactory orderThread = orderThreadFactoryBuilder.setNameFormat("---order---").build();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 30, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10),
                orderThread);
        for (int i = 0; i < 5; i++) {
            int fina = i;
            Runnable runnable = () -> {
                if (fina == 3) {
                    throw new RuntimeException("demo exception");
                } else {
                    System.out.println("runnable---" + fina + "---" + Thread.currentThread().getName());
                    System.out.println();
                }
            };
//            threadPoolExecutor.submit(runnable);
            threadPoolExecutor.execute(runnable);
//            Future<?> submit = threadPoolExecutor.submit(runnable);
//            System.out.println(submit.get());
        }
    }

If execute is used, the result is
It is found that when an exception occurs to a Thread, it will not affect the execution of other threads. Here is an exception thrown directly. You can also define the uncautexceptionhandler property of Thread itself through ThreadFactory, that is, the class that implements the uncautexceptionhandler interface.

Change to submit:

            Future<?> submit = threadPoolExecutor.submit(runnable);
            System.out.println(submit.get());


It is found that get will get a return result, which is null here, indicating that the thread is executed successfully without exception, and it will get stuck, that is, when the thread exception i=3 is found, the program throws an exception to stop. This is different from execute.

Change to submit form processing 2:

            threadPoolExecutor.submit(runnable);


You can't see the exception at all when you look at the log. It's obvious that an exception was thrown. There is no exception printing, but it does not affect the operation of other threads.

Here we simply record the different situations of the actual thread pool submission.

So far, I've started to think about a rule in the manual. Looking at the source code, I've simply understood why. It's mainly related to the work queue of the thread pool. How do the threads in the thread pool come from? I found the ThreadFactory,

1. The SynchronousQueue of newcachedthreadpool needs to learn
2.ThreadGroup needs to learn, and System.getSecurityManager()java security needs to learn.
3. Thread pool submit and execute are two ways to learn in source code layer.
4. The nature of thread, runnable and callable is different and related.

Posted by noobcody on Wed, 13 Nov 2019 00:12:45 -0800