High concurrent programming: thread pool

I. Overview

The thread pool first has several interfaces. First understand that the first is Executor, the second is ExecutorService, and the latter is the thread pool that uses ThreadPoolExecutor.

2. Executor

Executor can be understood from its name, executor, so it has a method called execution, and the executed thing is Runnable, so after this Executor has it, because it is an excuse, it can have many implementations, so we say, With Executor, we define a task on the spot. For example, Runnable means a command, and its definition and execution can be separated. Unlike we used to define a Thread, create a new Thread and then rewrite it. The Run method.start can be run, or if you wrote a Runnable in the past, you must also create a new Thread. The previous definition and operation are fixed, and it is hard-coded. You create a new Thread and let it run. Some students are still a new Thread, but they have a variety of new ways to play. You don’t need to specify each Thread yourself. You can define the way it runs, so it depends on how to define it. How do you implement the Executor interface? Here is the meaning of separating definition and operation, so this interface embodies this meaning, so this interface is relatively simple. As for whether you directly call run or create a new Thread, that is your own business Son.

* The {@code Executor} implementations provided in this package
 * implement {@link ExecutorService}, which is a more extensive
 * interface.  The {@link ThreadPoolExecutor} class provides an
 * extensible thread pool implementation. The {@link Executors} class
 * provides convenient factory methods for these Executors.
 *
 * <p>Memory consistency effects: Actions in a thread prior to
 * submitting a {@code Runnable} object to an {@code Executor}
 * <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a>
 * its execution begins, perhaps in another thread.
 *
 * @since 1.5
 * @author Doug Lea
 */
public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

三、ExecutorService

What does ExecutorService mean? He inherits from Executor. In addition, in addition to realizing that Executor can execute a task, he also improves a life cycle of the entire task executor. Take the thread pool as an example, a A bunch of threads in the thread pool are a bunch of workers. How do I end this thread after executing a task? The thread pool defines such methods. It implements the life cycle of the thread pool of some threads. The interface of Executor is extended, and the reality of the real thread pool is realized on the basis of ExecutorService. When we see this ExecutorService, you will find that in addition to the execution tasks of Executor, there are also submit submission tasks. The execution tasks are directly taken over and run immediately, while submit is thrown to this thread pool. When to run is determined by this thread pool. The decision is equivalent to asynchronous, as long as I throw it in, I don't care. Well, if you don’t care about it, when will he get results, which involves relatively new classes: such as Future, RunnableFuture, FutureTask, so in this I want to expand some basic concepts of threads for you. You have learned threads before. When defining a thread task can only implement the Runnable interface, then after 1.5 he added the Callable interface.

void shutdown();//结束
List<Runnable> shutdownNow();//马上结束
boolean isShutdown();//是否结束了
boolean isTerminated();//是不是整体都执行完了
boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;//等着结束,等多长时间,时间到了还不结束的话他就返回false

4. Callable

Let's take a look at the Callable document in the following code. He said that this interface is similar to java.lang.Runnable, so these two classes are designed to potentially run him in another thread, so you will know Callable and Runnable through this. Similarly, it can also be a thread to run it. Well, why do you need Callable when you have Runnable? It is very simple to see that the code Callable has a return value. The call method is equivalent to the run method in Runnable, and the method in Runnable returns The value is empty, and here there can be a return value, give you a calculation task, and finally you have to give me a result, this is called Callable, then since it can return a result, I can give this result to Store it and let me know when your old man finishes the calculation. I don't need to call his run in the original thread pool and wait here.

So with this Callable, there are many new ways to play. What is Callable, it is similar to Runnable, but Callable can have a return value.

package java.util.concurrent;
/**
 * A task that returns a result and may throw an exception.
 * Implementors define a single method with no arguments called
 * {@code call}.
 *
 * <p>The {@code Callable} interface is similar to {@link
 * java.lang.Runnable}, in that both are designed for classes whose
 * instances are potentially executed by another thread.  A
 * {@code Runnable}, however, does not return a result and cannot
 * throw a checked exception.
 *
 * <p>The {@link Executors} class contains utility methods to
 * convert from other common forms to {@code Callable} classes.
 *
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method {@code call}
 */
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

5. Future

After having this Callable, let’s look at an interface: Future, what does this Future represent? This Future represents how I can get the result after the Callable is executed. It will be encapsulated into a Future in. Future in the future, the future. After you execute it in the future, you can put this result into the result that may be executed in the future, so Future represents a result that is executed in the future.

Since Callable is basically designed for the thread pool, it is still troublesome if you want to write some small programs of Callable without the interface of the thread pool, so it is relatively simple to use some direct usage of the thread pool , we use it first, and then explain to you what it means after using it. Let's see how Future is used. When we read this ExecutorService, you will find that there is a submit method in it. This submit is an asynchronous submission task. After the task is submitted, how should the original thread run? Produce a result, where is the result, its return value is a Future, so you can only submit a Callable, which must have a return value, throw the Callable task to the thread pool, and the thread pool is finished executing, asynchronously, that is After handing over the task to the thread pool, what should my main thread do? Call the get method until there is a result, and then get will return. Callable is generally used in conjunction with thread pool and Future.

In fact, a more flexible usage is FutureTask, which is a Future and a Task at the same time. It turns out that this Callable can only be a Task and can only be a task, but it cannot be used as a Future. This FutureTask is equivalent to that I can use it as a task. At the same time, the result after the task is completed also exists in this object. Why can he do this? Because FutureTask implements RunnableFuture, and RunnableFuture implements Runnable and implements Future, so he is both a task and a Future. So this FutureTask is a better class to use. Remember this class, there will be WorkStealingPool, ForkJoinPool, etc., which will basically use the FutureTask class.

public class T06_00_Future {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		
		FutureTask<Integer> task = new FutureTask<>(()->{
			TimeUnit.MILLISECONDS.sleep(500);
			return 1000;
		}); //new Callable () { Integer call();}
		
		new Thread(task).start();
		
		System.out.println(task.get()); //阻塞
	}
}

六、CompletableFuture

The bottom layer of CompletableFuture uses ForkJoinPool.

Let's first look at its usage. Here is a small example. There is such a situation where this CompletableFuture can be used. This CompletableFuture is very flexible. There are many combinations of various results inside it. This CompletableFuture can combine various Different tasks of the same type, and then wait for the task to be executed to produce a result for a combination. Let's look directly at the code. If you write a website yourself, the website sells Gree air conditioners of the same type, and many people will compare prices when buying things. The service you provide is that I went to Taobao to find this Gree air conditioner. How much does the air conditioner cost, and then I start another thread to find out how much Gree air conditioner sells on Jingdong, and start a thread to find it on Pinduoduo. Finally, I will summarize for you how much each of these three places sells for, and then you Then choose where to buy. The following code simulates a method of going to other places to get prices. First of all, it will take a long time for you to visit other places, so I wrote a delay() to let him go to sleep for a random period of time, indicating that we want to connect to the Internet , we want the crawler to crawl the result to execute this time, and then print out how much time we slept before getting the result. For example, the result on Tmall is 1 yuan, the result on Taobao is 2 yuan, and the result on Jingdong is 3 yuan, all in all, it is how much money is analyzed by the data crawled by the web crawler. Then we need to simulate how to get and how to summarize. The first way of writing is the way I commented, which is written next to the card. Suppose I ran for 10 seconds on Tmall, took 10 seconds on Taobao, and ran on Jingdong. 5 seconds, it took a total of 25 seconds to come out. But if I use different threads, one by one they execute their calculations in parallel and the result is only 10 seconds.

But when you write with threads, you will have various troubles. For example, what should you do if the network reports an error during the process of going to Taobao? When you go to Jingdong just in time for his activities that day, concurrent access is very slow. What to do, you have to wait for all the threads to get a result before you can generate a result. If you want to do this, instead of writing your own thread for each thread, you need to consider various Such delay problems and various abnormal problems have a simple way of writing at this time, using a CompletableFuture. First of all, CompletableFuture is a Future, so it will store a result value that may be generated in the future, and the result The value is a Double, which will run a task, and then this task will finally produce a result, which will be stored in CompletableFuture, and the type of the result is Double.

Here I define three Futures, which represent Taobao, Jingdong, and Tmall respectively. I use a method of CompletableFuture called supplyAsync to generate an asynchronous task. This asynchronous task goes to Tmall to pull data for me. . You can imagine throwing him a task in a thread pool for him to execute, and when the execution is completed, his result will be returned to this futureTM. But the overall requirement is that all these futures have to be completed to show my final results.

Going down, there is another way of writing, that is, I can throw these three futures to a CompletableFuture for him to manage. When he manages, he can call the allOf method, which is equivalent to completing all the tasks in it, and finally join. You can continue to run down. So CompletableFuture not only provides easy-to-use task management, but also provides task stack management for managing a bunch of tasks. CompletableFuture also provides many ways of writing, such as the following way of writing Lambda expressions.

What is CompletableFuture? It is a management class for various tasks. All in all, CompletableFuture is a more advanced class that can help you manage various tasks you want at a very high level. For example, you can Make various combinations of tasks, what kind of result you want to execute after all tasks are completed, and what kind of result you want to execute after any task is completed, and it can provide a chained processing method Lambda Some ways of writing, how to deal with the results after getting the task.

public class T06_01_CompletableFuture {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long start, end;

        /*start = System.currentTimeMillis();
        priceOfTM();
        priceOfTB();
        priceOfJD();

        end = System.currentTimeMillis();
        System.out.println("use serial method call! " + (end - start));*/

        start = System.currentTimeMillis();

        CompletableFuture<Double> futureTM = CompletableFuture.supplyAsync(()->priceOfTM());
        CompletableFuture<Double> futureTB = CompletableFuture.supplyAsync(()->priceOfTB());
        CompletableFuture<Double> futureJD = CompletableFuture.supplyAsync(()->priceOfJD());

        CompletableFuture.allOf(futureTM, futureTB, futureJD).join();

        CompletableFuture.supplyAsync(()->priceOfTM())
                .thenApply(String::valueOf)
                .thenApply(str-> "price " + str)
                .thenAccept(System.out::println);

        end = System.currentTimeMillis();
        System.out.println("use completable future! " + (end - start));
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static double priceOfTM() {
        delay();
        return 1.00;
    }

    private static double priceOfTB() {
        delay();
        return 2.00;
    }

    private static double priceOfJD() {
        delay();
        return 3.00;
    }

    /*private static double priceOfAmazon() {
        delay();
        throw new RuntimeException("product not exist!");
    }*/

    private static void delay() {
        int time = new Random().nextInt(500);
        try {
            TimeUnit.MILLISECONDS.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("After %s sleep!\n", time);
    }
}

七、ThreadPoolExecutor

(1) Parameter analysis

Then there are various ThreadPoolExecutors, which use the thread pool as an execution unit and give him such a separate class, and then his seven parameters need to be memorized

1: corePoolSoze core thread number;

2: maximumPS maximum number of threads;

3: keepAliveTime survival time;

4: TimeUnit The unit of the survival time;

5: BlockingQueue task queue;

6: ThreadFactory thread factory;

7: RejectStrategy rejection strategy -> common four (Abort throws exception, Discard throws away, does not throw exception, DiscardOldest throws away the longest queue time, CallerRuns caller processing service);

(2), source code analysis

1. Explanation of common variables

Here are some explanations of commonly used variables. The first one is called control (ctl), and ctl represents two meanings. AtomicInteger is an int type, and the int type is 32 bits. The high three bits represent the state of the thread pool, and the low 29 bits represent the current state. How many threads are there in the thread pool. Then why doesn't he use two values? It must be optimized by himself. If we write it ourselves, it must be two values. What is the current state of our thread pool, and how many threads are currently running in it? Run and record it, but it combines these two values ​​into one, and the execution efficiency will be higher, because these two values ​​​​need thread synchronization, so he puts them in one value, as long as one thread executes Thread synchronization is enough, so here AtomicInteger is more efficient than synchronized when the number of threads is very large and the execution time is very short. The following 2 and 3 are a calculation of ctl. 4 is some 5 states of the thread pool

RUNNING: normal operation;

SHUTDOWN: Call the shutdown method and enter the shutdown state;

STOP: Call shutdownnow to stop him immediately;

TIDYING: Shutdown is called and then this thread is also executed. The process that is being sorted out now is called TIDYING;

TERMINATED: The entire thread is all over;

The following are some operations on ctl. runStateOf takes its state, workerCountOf calculates how many threads are working, and the 8th and 9th runStateLessThan, runStateAtLeast are some things to help write code.

// 1. `ctl`,可以看做一个int类型的数字,高3位表示线程池状态,低29位表示worker数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 2. `COUNT_BITS`,`Integer.SIZE`为32,所以`COUNT_BITS`为29
private static final int COUNT_BITS = Integer.SIZE - 3;
// 3. `CAPACITY`,线程池允许的最大线程数。1左移29位,然后减1,即为 2^29 - 1
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
// 4. 线程池有5种状态,按大小排序如下:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

// Packing and unpacking ctl
// 5. `runStateOf()`,获取线程池状态,通过按位与操作,低29位将全部变成0
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 6. `workerCountOf()`,获取线程池worker数量,通过按位与操作,高3位将全部变成0
private static int workerCountOf(int c)  { return c & CAPACITY; }
// 7. `ctlOf()`,根据线程池状态和线程池worker数量,生成ctl值
private static int ctlOf(int rs, int wc) { return rs | wc; }

/*
 * Bit field accessors that don't require unpacking ctl.
 * These depend on the bit layout and on workerCount being never negative.
 */
// 8. `runStateLessThan()`,线程池状态小于xx
private static boolean runStateLessThan(int c, int s) {
    return c < s;
}
// 9. `runStateAtLeast()`,线程池状态大于等于xx
private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}

2. Construction method

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.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    // 根据传入参数`unit`和`keepAliveTime`,将存活时间转换为纳秒存到变量`keepAliveTime `中
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

3. The process of submitting and executing the task

The execute method is relatively simple. Let’s briefly explain it. When execute executes the task, it is judged that the task is equal to an empty throw exception. This is very simple. The next step is to take the status value and calculate the thread in this value after getting the value Is the number of alive threads less than the number of core threads? If it is less than addWorker to add a thread, addWorker is a more difficult method. His second parameter refers to whether it is a core thread. Not enough to add core threads first, check this value again. We originally said that this thread starts to be 0 after it comes up, and a task starts a core thread, and the second is to put it in the queue after the number of core threads is full. Finally, the core thread is full, the queue is also full, and the non-core thread is started. If it is less than the number of threads, it will be added directly. The logic executed later is not less than, and if it is not less than the number of core threads, it will be directly thrown in. workQueue.offer is to throw him into the queue, and then check the status. The state value may be changed in the middle, so double checking is required. This is the same logic as the DC in the singleton mode we talked about before. isRunning, take this state again. After getting this state, a state switch is required here. If it is not in the Running state, it means that the shutdown command has been executed, and then the Running state will be converted to another state. In other cases, if workerCountOf is equal to 0 It means that there are no threads in it. If there are no threads, I will add non-core threads when the thread pool is running normally. These steps can be seen through the source code. If adding work itself doesn't work, reject him.

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    int c = ctl.get();
    // worker数量比核心线程数小,直接创建worker执行任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // worker数量超过核心线程数,任务直接进入队列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 线程池状态不是RUNNING状态,说明执行过shutdown命令,需要对新加入的任务执行reject()操作。
        // 这儿为什么需要recheck,是因为任务入队列前后,线程池的状态可能会发生变化。
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 这儿为什么需要判断0值,主要是在线程池构造方法中,核心线程数允许为0
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 如果线程池不是运行状态,或者任务进入队列失败,则尝试创建worker执行任务。
    // 这儿有3点需要注意:
    // 1. 线程池不是运行状态时,addWorker内部会判断线程池状态
    // 2. addWorker第2个参数表示是否创建核心线程
    // 3. addWorker返回false,则说明任务执行失败,需要执行reject操作
    else if (!addWorker(command, false))
        reject(command);
}

4. AddWorker source code analysis

addWorker involves some of his very detailed thoughts. It is not necessary for you to read through every little thought. As long as you can understand it, addWorker is to add threads. Threads are stored in containers and added to them. When you do this, you must know that there may be many threads that have to be thrown into it, so you must do synchronization. Then, because it wants to pursue efficiency, it will not use synchronized, it will use lock or spin, which will increase your code more. a level of complexity.

Let's read it roughly below. He did two steps in this, and the whole addWorker source code made two parts. The above two for loops are just the first step. This does one thing, adding 1 to the number of workers. Add a worker. The number is in the 29 bits of the 32 bits, and it is added by 1 in the case of multi-threading, so he performed two infinite loops to do this. The outer infinite loop sets the inner infinite loop. A lot of judgments have been made. If the status value does not match, return false. This status value cannot be added. When the status value does not match, it is greater than shutdown, indicating that you have already shut down, or remove the above status. , all states can add threads to it. Adding threads is another endless loop. First, calculate whether the current number of wc threads exceeds the capacity. Don’t add more if the capacity exceeds the capacity. Otherwise, use cas to add. If the addition is successful, it means that the first step is completed, and then retry the whole All break, the outer loop and the inner loop all jump out. If the addition is not successful, get it. After the get is finished, it will be processed again, continue retry, which is equivalent to the previous continuous trial until we add this number to 1 up to. Then, later, the work is really started, a new work, and the thread is started after the work is new. This work represents a thread. In fact, there is a thread in the work class, which is locked and is in a container. The state of multi-threading must be locked. After locking, check the state of the thread pool. Why do you need to check, because it may have been killed by other threads in the middle? Go in, start running after adding this thread, this is a general logic of addWorker.

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    // 外层自旋
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // 这个条件写得比较难懂,我对其进行了调整,和下面的条件等价
        // (rs > SHUTDOWN) || 
        // (rs == SHUTDOWN && firstTask != null) || 
        // (rs == SHUTDOWN && workQueue.isEmpty())
        // 1. 线程池状态大于SHUTDOWN时,直接返回false
        // 2. 线程池状态等于SHUTDOWN,且firstTask不为null,直接返回false
        // 3. 线程池状态等于SHUTDOWN,且队列为空,直接返回false
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        // 内层自旋
        for (;;) {
            int wc = workerCountOf(c);
            // worker数量超过容量,直接返回false
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 使用CAS的方式增加worker数量。
            // 若增加成功,则直接跳出外层循环进入到第二部分
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            // 线程池状态发生变化,对外层循环进行自旋
            if (runStateOf(c) != rs)
                continue retry;
            // 其他情况,直接内层循环进行自旋即可
            // else CAS failed due to workerCount change; retry inner loop
        } 
    }
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            // worker的添加必须是串行的,因此需要加锁
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                // 这儿需要重新检查线程池状态
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    // worker已经调用过了start()方法,则不再创建worker
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // worker创建并添加到workers成功
                    workers.add(w);
                    // 更新`largestPoolSize`变量
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            // 启动worker线程
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        // worker线程启动失败,说明线程池状态发生了变化(关闭操作被执行),需要进行shutdown相关操作
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

5. Custom rejection policy

An example of customizing a rejection policy, the code demonstration is as follows:

public class T14_MyRejectedHandler {
    public static void main(String[] args) {
        ExecutorService service = new ThreadPoolExecutor(4, 4,
                0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(6),
                Executors.defaultThreadFactory(),
                new MyHandler());
    }

    static class MyHandler implements RejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            //log("r rejected")
            //save r kafka mysql redis
            //try 3 times
            if(executor.getQueue().size() < 10000) {
                //try put again();
            }
        }
    }
}

八、SingleThreadPool

Look at the code below. The first one is called SingleThreadPool. You can tell from the name that there is only one thread in this thread pool. This one-thread thread pool can ensure that the tasks we throw in are executed sequentially.

Someone will definitely ask such a question, why is there a single-threaded thread pool? The first thread pool has a task queue; the life cycle management thread pool can help you;

九、SingleThreadPool

public class T07_SingleThreadPool {
	public static void main(String[] args) {
		ExecutorService service = Executors.newSingleThreadExecutor();
		for(int i=0; i<5; i++) {
			final int j = i;
			service.execute(()->{
				System.out.println(j + " " + Thread.currentThread().getName());
			});
		}
	}
}

10. Cached Pool

Let's look at the second CachedPool. Its source code is actually a new ThreadPoolExecutor. It has no core threads, and the maximum thread can have many, many threads. Then, no one cares about it for 60 seconds, so it is recycled. Its task queue SynchronousQueue is used, and its thread factory is not specified. It is the default thread factory, and the rejection strategy is not specified. It is the default rejection strategy.

We can see the characteristics of CachedThreadPool, that is, when you come to a task, I will start a thread for you. Of course, the premise is that there are threads in my thread pool and it has not reached the 60-second recovery time. When you come to a task, if If there are threads, I will use the existing thread pool, but when a new task comes, if other threads are busy, I will start a new one. How can some students say that if I come to a task, I will throw it into the task queue? Let’s analyze our CachedThreadPool. The task queue it uses is synchronousQueue. It is a Queue with an empty hand-handed capacity. That is, when you come to something, there must be a thread to take it away. Otherwise, the thread that I submit the task will block from here. Living. SynchronousQueue can also be extended to multiple threads hand-delivered, multiple producers and multiple consumers need to hand-delivered called TransferQueue. This CachedThreadPool is such a thread pool. When a new task comes, it must be executed immediately. If there is no thread empty, I will create a new thread. Then Ali does not recommend the use of this thread pool, because the number of threads will be started too much, which is basically close to no upper limit.

Look at this small program, first print out the service, and finally print out the service, our task is to sleep for 500 milliseconds, then print the thread pool, and print his name. Run it, and you can see some status of the thread pool by printing the output of toString of the thread pool.

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

package com.mashibing.juc.c_026_01_ThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class T08_CachedPool {
	public static void main(String[] args) throws InterruptedException {
		ExecutorService service = Executors.newCachedThreadPool();
		System.out.println(service);
		for (int i = 0; i < 2; i++) {
			service.execute(() -> {
				try {
					TimeUnit.MILLISECONDS.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName());
			});
		}
		System.out.println(service);
		TimeUnit.SECONDS.sleep(80);
		System.out.println(service);
	}
}

Eleven, FixedThreadPool

Look at its name, fixed means fixed, that is, a fixed number of threads, FixedThreadPool specifies a parameter, how many threads there are, you can see that its core thread and maximum thread are fixed, because its maximum thread and The core threads are all fixed, so there is no recycling, so specify him as 0, and LinkedBlockingQueue is used here

Let's take a look at this small example of FixedThreadPool. What is the advantage of using a fixed thread pool, that is, you can perform parallel calculations. So what is the difference between parallelism and concurrency here? concurrent vs parallel : concurrency refers to task submission , parallelism refers to task execution; parallelism is a subset of concurrency. Parallelism means that multiple CPUs can process at the same time, and concurrency means that multiple tasks come at the same time. To understand this concept. FixedThreadPool can indeed allow your tasks to be processed in parallel, so the efficiency can be really improved during parallel processing. Look at this method isPrime to judge whether a number is a prime number, and then write another getPrime method, specify a real position, and an end position to take out a part of the prime number in the middle, mainly to split the task. Calculate how many of the numbers from 1 to 200,000 are prime numbers getPrime, and calculate the time. Only one of our main threads runs, but since we have learned multi-threading, we can completely divide this task into many, many subtasks. Multi-threading to run together, how many CPUs do I have, my machine has 4 cores, this depends on the number of your machines, start a thread pool with a fixed size, and then calculate separately, and hand over different stages to different The task is thrown into the submit, which is asynchronous. When you get the get, you will know how many there are in it. After all the get is completed, it means that all the threads know the result. Finally, we calculate the time and use these two calculation methods It can be compared whether the parallel method is fast or the serial method is fast. If you think about it with your thighs, you can know that it must be much faster to use the thread pool. Therefore, this is also a way to use the thread pool.

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

/**
 * 线程池的概念
 * nasa
 */
package com.mashibing.juc.c_026_01_ThreadPool;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class T09_FixedThreadPool {
   public static void main(String[] args) throws InterruptedException, ExecutionException {
      long start = System.currentTimeMillis();
      getPrime(1, 200000); 
      long end = System.currentTimeMillis();
      System.out.println(end - start);
      
      final int cpuCoreNum = 4;
      
      ExecutorService service = Executors.newFixedThreadPool(cpuCoreNum);
      
      MyTask t1 = new MyTask(1, 80000); //1-5 5-10 10-15 15-20
      MyTask t2 = new MyTask(80001, 130000);
      MyTask t3 = new MyTask(130001, 170000);
      MyTask t4 = new MyTask(170001, 200000);
      
      Future<List<Integer>> f1 = service.submit(t1);
      Future<List<Integer>> f2 = service.submit(t2);
      Future<List<Integer>> f3 = service.submit(t3);
      Future<List<Integer>> f4 = service.submit(t4);
      
      start = System.currentTimeMillis();
      f1.get();
      f2.get();
      f3.get();
      f4.get();
      end = System.currentTimeMillis();
      System.out.println(end - start);
   }
   
   static class MyTask implements Callable<List<Integer>> {
      int startPos, endPos;
      
      MyTask(int s, int e) {
         this.startPos = s;
         this.endPos = e;
      }
      
      @Override
      public List<Integer> call() throws Exception {
         List<Integer> r = getPrime(startPos, endPos);
         return r;
      }
      
   }
   
   static boolean isPrime(int num) {
      for(int i=2; i<=num/2; i++) {
         if(num % i == 0) return false;
      }
      return true;
   }
   
   static List<Integer> getPrime(int start, int end) {
      List<Integer> results = new ArrayList<>();
      for(int i=start; i<=end; i++) {
         if(isPrime(i)) results.add(i);
      }
      return results;
   }
}

12. Scheduled Pool

ScheduledPool timed task thread pool is a timer task we have learned before, and this task will be executed after a certain period of time. This is a thread pool that we use specifically to perform scheduled tasks. Looking at the source code, when we newScheduledThreadPool, it returns ScheduledThreadPoolExecutor, and then in ScheduledThreadPoolExecutor, he calls super, and his super is ThreadPoolExecutor, which is ThreadPoolExecutor in essence, so it is nothing else, and the parameters are still the seven parameters of ThreadPool. This is such a thread pool specially used for given timing tasks, just understand it.

Looking at the program, the core thread of newScheduledThreadPool is 4. In fact, there are some useful methods in it, such as scheduleAtFixedRate, how long does it take to execute this task at a fixed frequency, and you can flexibly control the time in this way. , the first parameter (Delay) how long it takes to push back before the first task is executed; the second (period) how long is the interval; the third parameter is the time unit;

//源码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
}

package com.mashibing.juc.c_026_01_ThreadPool;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class T10_ScheduledPool {
   public static void main(String[] args) {
      ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
      service.scheduleAtFixedRate(()->{
         try {
            TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName());
      }, 0, 500, TimeUnit.MILLISECONDS);
   }
}

Thirteen, ForkJoinPool

ForkJoinPool is such a thread pool. It is suitable for dividing large tasks into small tasks one by one to run. The small tasks are still relatively large, and then cut, not necessarily two, but also three or four. After cutting this task and executing it, a summary is required, as shown in the figure below. Of course, there are some printout tasks that do not need to return a value, but in many cases we need to perform a summary of the results. The subtasks are summarized to the parent task, and the parent task The tasks are finally aggregated to the root task, and finally we get all the results. This process is called join, so this thread pool is called ForkJoinPool.

So how do we define this task? When we originally defined the task, we inherited it from Runnable. Here we generally need to define it as a specific type when we implement ForkJoinPool. This type is a task that must be able to fork, so it is defined as a special type. Type of task, this is called ForkJoinTask, but in practice, this ForkJoinTask is relatively primitive, we can use this RecursiveAction, there are two kinds of it, the first is called RecursiveAction recursion, why is it called recursion, because our large tasks can be cut into small tasks, Small tasks can also be cut into small tasks until my conditions are met, which implies a recursive process, so it is called RecursiveAction, which is a task without a return value.

Let’s take a look at the small program of tasks without return values. I have created an array with a length of 1 million. There are many numbers in this array. These numbers are all generated through Random. Next, I will compare a bunch of Calculate the sum of the numbers. If I use a single thread to calculate, I can calculate it like this: Arrays.stream(nums).sum(). This is a single thread, and this time will be relatively long. We can perform multithreaded calculations, just Like the FixedThreadPool we wrote before, now we can use ForkJoinPool to do calculations. When calculating, I will go to the smallest task slice. The number is no more than 50,000, so you don’t need to divide it. RecursiveAction is our task, which is used for summing. Since the array is sliced, a starting position and an ending position are defined, and then the calculation is performed. If the number of fragments in our array is less than the minimum number we defined, that is, if the number is less than 50,000, we can directly calculate it. Otherwise, we will cut half of it in the middle, and divide the current task into two parts after cutting. Task, and then let the two subtasks fork fork. These tasks have their own characteristics, which are the background threads behind them, so I need to use a blocking operation to prevent the current main function from exiting, otherwise all threads will exit once it exits, ok, this is called a task with no return value .

For tasks that return values, you can inherit from RecursiveTask, see the AddTaskRet method below.

public class T12_ForkJoinPool {
	static int[] nums = new int[1000000];
	static final int MAX_NUM = 50000;
	static Random r = new Random();
	
	static {
		for(int i=0; i<nums.length; i++) {
			nums[i] = r.nextInt(100);
		}
		System.out.println("---" + Arrays.stream(nums).sum()); //stream api
	}
	
	static class AddTask extends RecursiveAction {
		int start, end;
		AddTask(int s, int e) {
			start = s;
			end = e;
		}

		@Override
		protected void compute() {
			if(end-start <= MAX_NUM) {
				long sum = 0L;
				for(int i=start; i<end; i++) sum += nums[i];
				System.out.println("from:" + start + " to:" + end + " = " + sum);
			} else {

				int middle = start + (end-start)/2;

				AddTask subTask1 = new AddTask(start, middle);
				AddTask subTask2 = new AddTask(middle, end);
				subTask1.fork();
				subTask2.fork();
			}
		}
	}

	
	static class AddTaskRet extends RecursiveTask<Long> {
		private static final long serialVersionUID = 1L;
		int start, end;
		
		AddTaskRet(int s, int e) {
			start = s;
			end = e;
		}

		@Override
		protected Long compute() {
			if(end-start <= MAX_NUM) {
				long sum = 0L;
				for(int i=start; i<end; i++) sum += nums[i];
				return sum;
			} 
			
			int middle = start + (end-start)/2;
			
			AddTaskRet subTask1 = new AddTaskRet(start, middle);
			AddTaskRet subTask2 = new AddTaskRet(middle, end);
			subTask1.fork();
			subTask2.fork();
			
			return subTask1.join() + subTask2.join();
		}
	}
	
	public static void main(String[] args) throws IOException {
		/*ForkJoinPool fjp = new ForkJoinPool();
		AddTask task = new AddTask(0, nums.length);
		fjp.execute(task);*/

		T12_ForkJoinPool temp = new T12_ForkJoinPool();

		ForkJoinPool fjp = new ForkJoinPool();
		AddTaskRet task = new AddTaskRet(0, nums.length);
		fjp.execute(task);
		long result = task.join();
		System.out.println(result);
		//System.in.read();
	}
}

Fourteen, workers

(I. Overview

Behind this is a simple explanation of the worker class, which includes a thread and a task, and then records how many tasks my worker has done, etc.

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;

    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
    /** Per-thread task counter */
    volatile long completedTasks;

    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        // 这儿是Worker的关键所在,使用了线程工厂创建了一个线程。传入的参数为当前worker
        this.thread = getThreadFactory().newThread(this);
    }

    /** Delegates main run loop to outer runWorker  */
    public void run() {
        runWorker(this);
    }

    // 省略代码...
}

(2), core thread execution logic - runworker

Runwork is how to perform this task after actually starting the thread, and similarly, lock it. What's more interesting about this is that the worker inherits from AbstractQueuedSynchronizer and implements Runnable at the same time, indicating that the worker can be run in a thread. At the same time, it is a lock itself and can be synchronized. In addition, it can be executed by a thread. A task, why is it itself a lock? This worker can be considered as a worker waiting to be executed. Many tasks can throw content into it, that is to say, there will be multiple threads to access this object. , when multiple threads access this object, he simply made a lock for himself, so don't define a lock by yourself, so when you need to throw tasks into this worker, specify that my thread is the one you execute When it comes to threads, well, it’s ok to go to the lock through the worker itself. There is no need to go to new other locks at all, so when you run the worker, you need to lock it first. If you want to run it, you have to lock it to execute it, otherwise other threads It is possible to occupy this worker, and there are a bunch of executions below. After the execution is completed, the unlock will come out, and after the execution is completed, ++.

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    // 调用unlock()是为了让外部可以中断
    w.unlock(); // allow interrupts
    // 这个变量用于判断是否进入过自旋(while循环)
    boolean completedAbruptly = true;
    try {
        // 这儿是自旋
        // 1. 如果firstTask不为null,则执行firstTask;
        // 2. 如果firstTask为null,则调用getTask()从队列获取任务。
        // 3. 阻塞队列的特性就是:当队列为空时,当前线程会被阻塞等待
        while (task != null || (task = getTask()) != null) {
            // 这儿对worker进行加锁,是为了达到下面的目的
            // 1. 降低锁范围,提升性能
            // 2. 保证每个worker执行的任务是串行的
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            // 如果线程池正在停止,则对当前线程进行中断操作
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            // 执行任务,且在执行前后通过`beforeExecute()`和`afterExecute()`来扩展其功能。
            // 这两个方法在当前类里面为空实现。
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                // 帮助gc
                task = null;
                // 已完成任务数加一 
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 自旋操作被退出,说明线程池正在结束
        processWorkerExit(w, completedAbruptly);
    }
}

(三)、WorkStealingPool

This WorkStealingPool is another thread pool. The core is very simple. The thread pool we talked about is a collection of threads and then goes to another task queue to fetch tasks and execute them. WorkStealing refers to the difference from the original thread pool. Each thread has its own separate queue, so when tasks are continuously thrown in, it will continue to accumulate on the queue of each thread, and let a certain thread execute its own tasks. Just go back and steal on another thread, you give me one and I use it, so this is called WorkStealing.

So in the end, this thread pool method shares the same task queue as we originally talked about. What are the good and bad points between them? In this way, if a certain thread is occupied for a long time, and the task is particularly heavy, other threads can only be empty, and there is no way for him to help the thread with a particularly heavy task. But this one is more flexible. If I have another task to clear when the task is particularly heavy, it doesn’t matter, I can give you some tasks, so this is a pool like WorkStealing.

Looking at this source code, he actually created a new ForkJoinPool, so it is essentially a ForkJoinPool, so we only need to clarify the meaning of this WorkStealing after this ForkJoinPool, and look down.

//源码
public static ExecutorService newWorkStealingPool() {
  return new ForkJoinPool
    (Runtime.getRuntime().availableProcessors(),
     ForkJoinPool.defaultForkJoinWorkerThreadFactory,
     null, true);
}

package com.mashibing.juc.c_026_01_ThreadPool;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class T11_WorkStealingPool {
   public static void main(String[] args) throws IOException {
      ExecutorService service = Executors.newWorkStealingPool();
      System.out.println(Runtime.getRuntime().availableProcessors());

      service.execute(new R(1000));
      service.execute(new R(2000));
      service.execute(new R(2000));
      service.execute(new R(2000)); //daemon
      service.execute(new R(2000));
      
      //由于产生的是精灵线程(守护线程、后台线程),主线程不阻塞的话,看不到输出
      System.in.read(); 
   }

   static class R implements Runnable {

      int time;

      R(int t) {
         this.time = t;
      }

      @Override
      public void run() {
         
         try {
            TimeUnit.MILLISECONDS.sleep(time);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println(time  + " " + Thread.currentThread().getName());  
      }
   }
}

(4), summary

worker class

This work itself is Runnable and AQS at the same time. You can ignore AQS for now, because it can be realized in other ways. It is a Runnable task that you come in, and he uses this Runnable to package it for you. Why do you need to package it again, because there are many states in it that need to be recorded. You didn’t have it in this task before, and this thing must be in It runs in a thread, so he wraps it for you again with Runnable. Then this work class will record a member variable, this member variable is thread. Which thread is executing my object? Many threads will grab it, so this is why AQS is used. In addition, you also need to lock during your entire execution process. Otherwise, if other threads come in, it is very possible to ask your work to perform other tasks. At this time, you also need to lock, so AQS needs of. This is the work class, you can simply treat it as a thread class, and then this thread class performs your own tasks.

execute

Followed by the execute method, three steps

The first step: the number of core threads is not enough, start the core;

The second step: the core thread is enough to add a queue;

The third part: Both the core thread and the queue are full, and the non-core thread;

addWorker

First: add 1 to count first;

Second: It is the real addition to the task and start;

Guess you like

Origin blog.csdn.net/weixin_55229531/article/details/131153344