Catalog
- Introduction to ForkJoinPool
- What is ForkJoinPool?
- What are the advantages and disadvantages of ThreadPoolExecutor?
- When to use the ForkJoinPool?
- Example DEMO
- Involving core classes or components
- ForkJoinPool
- ForkJoinTask
- ForkJoinWorkerThread
- WorkQueue
- Execution process
- Execution process
- Task stealing
- Source code analysis
- Add tasks
- fork task
- join task
- Supplementary part
details
1. Introduction to forkjoinpool
1.1 what is ForkJoinPool?
ForkJoinPool is a newly added JUC thread pool tool class in JDK 1.7. It also inherits AbstractExcutorService, as shown in the following figure
Before we start to introduce this class, let's guess the left and right of this class from the literal meaning: fork: fork; join: join, join, pool: pool; translation: join the branching pool, or join the branching pool. Well, this translation pit Dad!!!!
No, no, no, that's not the case. We can add some conjectures appropriately. For example, in JUC, it must have something to do with concurrency, so can we translate it like this?
"A thread pool that splits tasks and merges results.".
1.2 what are the advantages and disadvantages of ThreadPoolExecutor?
From the above we know that ForkJoinPool is a thread pool, so what's the difference between it and our common thread pool executor thread pool? First, look at the structure of the two thread pools:
Then we analyze:
The ThreadPoolExecutor will first hand over the task to the core thread for execution. If the number of core threads is not enough, it will be put into the queue. The threads in the thread pool will uniformly get the task from the task queue and execute it. We can see from such a design that the execution time of the ThreadPoolExecutor is uncertain, such as: network IO operation, timed task, etc
ForkJoinPool is a task queue corresponding to a thread. Normally, each task only needs to perform its own tasks. If the task list corresponding to a thread is empty, tasks in the task list of other threads will be stolen at random (in order to reduce the task competition, tasks will be obtained from the other end of the task). Of course, the above figure does not show it. This not only reduces the task competition, but also makes full use of CPU resources. We can see from this design that: ForkJoinPool is very suitable for programs with more tasks and shorter execution time, such as filtering elements in the collection (the bottom layer of JDK1.8 stream is ForkJoinPool yo)
Finally, add: The appearance of ForkJoinPool is not to replace thread pools such as ThreadPoolExecutor, but to supplement their functions. Each has its own usage scenarios. Different thread pools can be used according to different scenarios
1.3 when to use the ForkJoinPool?
- Refer to the analysis and supplement of ForkJoinPool in 1.2: ForkJoinPool is very suitable for programs with more tasks and shorter events, such as filtering elements in the collection (the bottom layer of JDK1.8 stream is ForkJoinPool yo);
- The appearance of ForkJoinPool is not to replace thread pools such as ThreadPoolExecutor, but to supplement their functions. Each has its own usage scenarios. Different thread pools can be used according to different scenarios.
2 example DEMO
Next, take the illegal words in filter as an example (the prohibited words must be used in reality, this article just makes a simple DEMO). First, let's talk about the specific test method: there is a string of text and a known set of prohibited words. If the text in the text is in the set of prohibited words, calculate the number of times that the prohibited words appear and return
// test method public class ForkJoinPoolLearn { public final static String CONTENT = "Wow, so handsome! Wow, yeah, I love it! Wow, can I have a signature?"; public static final int THRESHHOLD = 5; public static List<String> BLACK_WORDS = new ArrayList<>(); static { BLACK_WORDS.add("wow"); } public static void main(String[] args) { //Use ForkJoinPool to perform tasks // Object with return value System.out.println("About to test object with return value..."); ForkJoinPool forkJoinPool = new ForkJoinPool(); MyRecursiveTask myRecursiveTask = new MyRecursiveTask(0, ForkJoinPoolLearn.CONTENT.length(), Arrays.asList(ForkJoinPoolLearn.CONTENT.split(""))); Integer value = forkJoinPool.invoke(myRecursiveTask); System.out.println(String.format("Character string:%s Number of prohibited words included:%s,Prohibited words:%s", CONTENT, value, StringUtils.join(BLACK_WORDS, ","))); } } // Submit task class public class MyRecursiveTask extends RecursiveTask<Integer> { private int startIndex; private int endIndex; private List<String> words; public MyRecursiveTask(int startIndex, int endIndex, List<String> words) { this.startIndex = startIndex; this.endIndex = endIndex; this.words = words; } @Override protected Integer compute() { int sum = 0; if ((endIndex - startIndex) <= ForkJoinPoolLearn.THRESHHOLD) {// If the length cannot be further divided, start filtering for (int i = startIndex; i < words.size() && i < endIndex; i++) { String word = words.get(i); if (ForkJoinPoolLearn.BLACK_WORDS.contains(word)) { sum += 1; } } } else {// If the length is too long, fork handles two tasks int middle = (startIndex + endIndex) / 2; MyRecursiveTask left = new MyRecursiveTask(startIndex, middle, words); MyRecursiveTask right = new MyRecursiveTask(middle, endIndex, words); left.fork(); right.fork(); Integer leftValue = left.join(); Integer rightValue = right.join(); sum = leftValue + rightValue; } return sum;// Return the calculated value } }
The result of the above example: "string: Wow, so handsome! Wow, yeah, I love it! Wow, can I have a signature Including the number of forbidden words: 3, the number of forbidden words: Wow "
3. Involving core classes or components
3.1 ForkJoinPool
This class is the implementation class of ExecutorService, which is mainly responsible for the management of working threads, the maintenance of task queues, and the control of the whole task scheduling process;
Construction method: Three construction methods are provided:
- Non parametric structure
public ForkJoinPool() { this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),defaultForkJoinWorkerThreadFactory, null, false); }
- An int type construct
public ForkJoinPool(int parallelism) { this(parallelism, defaultForkJoinWorkerThreadFactory, null, false); }
- For the construction of multiple parameters, the code is as follows:
public ForkJoinPool(int parallelism, ForkJoinWorkerThreadFactory factory, UncaughtExceptionHandler handler, boolean asyncMode) { this(checkParallelism(parallelism),checkFactory(factory),handler,asyncMode ? FIFO_QUEUE : LIFO_QUEUE,"ForkJoinPool-" + nextPoolId() + "-worker-"); checkPermission(); }
In fact, the above three construction methods are the following private construction methods called:
private ForkJoinPool(int parallelism, ForkJoinWorkerThreadFactory factory, UncaughtExceptionHandler handler, int mode, String workerNamePrefix) { this.workerNamePrefix = workerNamePrefix; this.factory = factory; this.ueh = handler; this.config = (parallelism & SMASK) | mode; long np = (long)(-parallelism); // offset ctl counts this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK); }
Explain the meaning of each parameter:
- Parallelism: the degree of parallelism. Currently, it is interpreted as the number of threads in the thread pool (but this statement is not completely correct, and it will be explained in detail later)
- Factory: create a factory interface for the ForkJoinWorkerThread
- Handler: exception handler
- Mode: select whether the task is in FIFO or LIFO mode, 0: LIFO; 1: FIFO;
- workerNamePrefix: the name of the ForkJoinWorkerThread
- ctl: core control field of thread pool
Submit task by
- Submit: there is a return value. You can submit tasks of runnable, callable, and forkjointask types
- invoke: there is a return value. You can submit tasks of type ForkJoinTask, and automatically judge whether there is a return value according to different forkjointasks
- execute: no return value, runnable and forkjointask type tasks can be submitted
Because the implementation of this class is too complex, the class structure diagram will not be pasted here. Small partners can open the IDE for research
3.2 ForkJoinTask
This class is the implementation class of the Future interface. fork is its core method, which is used to decompose tasks and execute them asynchronously. The join method runs only after the task result is calculated, which is used to merge or return the calculation results. The following two commonly used implementation classes are as follows:
- Recursive action: indicates a ForkJoin task that does not return a result
- Recursive task: indicates a ForkJoin task with returned results. This class is used to submit tasks in DEMO
3.3 ForkJoinWorkerThread
This is a subclass of Thread, which performs tasks as a Worker in the Thread pool;
Each working thread in the thread pool maintains its own task queue. The working thread takes priority in processing tasks in its queue (LIFO or FIFO order, determined by the parameter mode when the thread pool is constructed). When its queue is empty, it randomly steals tasks in other queues in the order of FIFO.
3.4 WorkQueue
This is the internal class of ForkJoinPool, which is used to save tasks;
4. Execution process
4.1 execution process
The following figure shows the overall execution process of the ForkJoinPool. The following source code analysis will be introduced in detail in combination with the figure. After the introduction, the specific method level call chart will be supplemented
4.2 task stealing
To be added
5. Source code analysis
So far, the general execution process has been understood. Next, let's talk about the source code to understand the specific execution process
5.1 add task
To be added
5.2 fork task
public final ForkJoinTask<V> fork() { Thread t; if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) // The current thread is the ForkJoinWorkerThread. Then you can perform your own fork tasks. It's just that I have to fill the hole I dug while crying ((ForkJoinWorkerThread)t).workQueue.push(this); else // If no one is in charge of the task, it will be added to the task queue, and finally a random thread will be found to execute it ForkJoinPool.common.externalPush(this); return this; }
5.3 join task
public final V join() { int s; if ((s = doJoin() & DONE_MASK) != NORMAL) // After the error, handle the error reportException(s); // Refer to add task section return getRawResult(); }