“This article has participated in the call for good writing activities, click to view the back end, big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!”
Fat Supervisor: What do you know about ForkJoinPool, Rice
Understand, this is the default thread pool used in Caffeine
Fat exec: How is it different from ThreadPoolExecutor?
Handsome little rice rice: (⊙ O ⊙)… I don’t understand that
Fei Fei’s supervisor: Are there any application scenarios? Can we incorporate that into our game? Is there any downside?
Handsome little rice rice: (⊙ O ⊙)… I’ll study it over the weekend
So that’s me, handsome little rice rice was little fatty trouble daily.
image-20210620115400862
Every time I see this, I think of FuckXXX.
I have known about this function for quite some time, such as thread pools provided by default in Caffeine and Netty, but I haven’t studied it yet.
Over the weekend, I finally figured out ForkJoinPool, and I could finally face the liver with little Fat
image-20210620154701046
After reading the article, you can probably understand the following points:
-
What is ForkJoinPool
-
How is it different from ThreadPoolExecutor
-
What are the application scenarios
-
Advantages and disadvantages
-
Frequently seen exam
Fatty: didn’t you spend the weekend studying ForkJoinPool? Tell me what it is. What is it?
* Handsome small rice rice: * Yes, yes, can work hard, now have a thorough study,
ForkJoinPool is a thread pool introduced in JDK7. The main idea is to split large tasks into smaller ones (forks) and then combine them into a single result (join), much like MapReduce.
It also provides basic thread pool functions, such as setting the maximum number of concurrent threads, supporting task queuing, thread pool stopping, and thread pool usage monitoring. AbstractExecutorService is a subclass of AbstractExecutorService, which introduces the “work stealing” mechanism and provides better processing performance on multi-CPU computers.
In order to let you see this process clearly, I have drawn a flow chart for you
image-20210620120440613
Small fat fat: what call “work steal” mechanism, so diaosi of
* Handsome little Rice rice: * As the name suggests, ForkJoinPool provides a more efficient mechanism for using threads, stealing, and stealing. While ThreadPoolExecutor is still storing tasks in a single queue, When a task is added to a ForkJoinPool, the number of threads in the queue is equal to the number of threads in the queue. When a thread finishes early, it “steals” unfinished tasks from other threads at the end of the queue. Computers with more cpus perform better, that’s how strong they are.
image-20210620123535386
Small fat fat: I wipe, good strong, principle is what
* Handsome small rice rice: * principle ah, I list it
- A ForkJoinPool maintains a work queue for each worker thread.
WorkQueue
), this is a double-ended queue (Deque) where the objects are tasks (ForkJoinTask
). - Each worker thread generates a new task at run, usually by calling fork(), at the end of the work queue, and the worker thread processes its own work queue usingLIFOThat is, take the task from the back of the queue and execute it every time.
- Each worker thread in dealing with their own work queue at the same time, will try to steal a task, the theft or from just submit to the task of the pool, or from other work thread work queue, the task of stealing work queue in other threads team first, that is to say, the worker thread in stealing other tasks, the worker thread is usedFIFOWay.
- When join() is encountered, if the task that requires the join has not yet completed, the other tasks are processed first and wait for them to complete.
- Go to sleep when you have neither your own mission nor one to steal.
* I see. How else is it different from ThreadPoolExecutor
*(⊙ O ⊙)… The biggest difference is the work stealing mechanism. The other, let me think about it, is our thread pool model in the game
In the ThreadPoolExecutor thread pool that we use in the game, the corresponding thread pool is specified. For example, thread A can only process data from one set of players, and thread B can only process data from another set of players. ForkJoinPool uses the idea of mortuse, and the idea of stealing is parallel computation. There is no clear division of work for the corresponding threads, which alone is not suitable as a vehicle for our threading model.
Xiao Feifei: yeah, that’s right. What’s the application scenario
ForkJoinPool is best used for computation-intensive tasks. If there are I/O, interthread synchronization, sleep(), and other conditions that cause a thread to block for long periods of time, use the ManagedBlocker or not.
In order to let Xiao Fei Fei have a clear view of this application scenario, I opened idea and wrote the following code angrily (actually I saw the example from the Internet).
Solution 1: the mundane for loop solution
public interface Calculator { /** * add all numbers **@param numbers * @returnThe sum of the * / long sumUp(long[] numbers); }Copy the code
Daily code
public class ForLoopCalculator implements Calculator { @Override public long sumUp(long[] numbers) { long total = 0; for (long i : numbers) { total += i; } return total; }}
Copy the code
Perform class
public static void main(String[] args) { long[] numbers = LongStream.rangeClosed(1.10000000).toArray(); Instant start = Instant.now(); Calculator calculator = new ForLoopCalculator(); long result = calculator.sumUp(numbers); Instant end = Instant.now(); System.out.println("Time:" + Duration.between(start, end).toMillis() + "ms"); System.out.println("Results are:"+ result); } Output: Time: 10ms50000005000000
Copy the code
Solution 2: Implement the ExecutorService in multi-threaded mode
public class ExecutorServiceCalculator implements Calculator { private int parallism; private ExecutorService pool; public ExecutorServiceCalculator(a) { parallism = Runtime.getRuntime().availableProcessors(); / / CPU core The default used a number of CPU core pool = Executors. NewFixedThreadPool (parallism); Private static class implements Callable
{private static class implements Callable
{private static class implements Callable []; private int from; private int to; public SumTask(long[] numbers, int from, int to) { this.numbers = numbers; this.from = from; this.to = to; } @Override public Long call() { long total = 0; for (int i = from; i <= to; i++) { total += numbers[i]; } return total; } } @Override public long sumUp(long[] numbers) { List
> results = new ArrayList<>(); Int part = numbers. Length/parallism; int part = numbers. Length/parallism; for (int i = 0; i < parallism; i++) { int from = i * part; Int to = (I == parallism-1)? numbers.length - 1 : (i + 1) * part - 1; Add (pool.submit(new SumTask(numbers, from, to))); } // The get() method is blocking. CompletableFuture can be used to optimize the new JDK1.8 feature. for (Future
f : results) { try { total += f.get(); } catch (Exception ignore) { } } return total; }}
Copy the code
Perform class
public static void main(String[] args) { long[] numbers = LongStream.rangeClosed(1.10000000).toArray(); Instant start = Instant.now(); Calculator calculator = new ExecutorServiceCalculator(); long result = calculator.sumUp(numbers); Instant end = Instant.now(); System.out.println("Time:" + Duration.between(start, end).toMillis() + "ms"); System.out.println("Results are:" + result); Output: Time: 30ms Result: 50000005000000
Copy the code
Solution 3: Use ForkJoinPool (Fork/Join)
public class ForkJoinCalculator implements Calculator { private ForkJoinPool pool; // Execute task RecursiveTask: return value RecursiveAction: Private static class extends RecursiveTask
{private static class extends RecursiveTask [] numbers; private int from; private int to; public SumTask(long[] numbers, int from, int to) { this.numbers = numbers; this.from = from; this.to = to; } // This method is the core of ForkJoin: Override protected Long compute() {Override protected Long compute() {Override protected Long compute() {Override protected Long compute() { If (to-from < 6) {long total = 0; for (int i = from; i <= to; i++) { total += numbers[i]; } return total; } middle = (from + to) / 2;} middle = (from + to) / 2; SumTask taskLeft = new SumTask(numbers, from, middle); SumTask taskRight = new SumTask(numbers, middle + 1, to); taskLeft.fork(); taskRight.fork(); return taskLeft.join() + taskRight.join(); }}} Public ForkJoinCalculator() {// You can also use the public thread pool forkJoinpool.monpool () : // pool = ForkJoinPool.commonPool() pool = new ForkJoinPool(); } @Override public long sumUp(long[] numbers) { Long result = pool.invoke(new SumTask(numbers, 0, numbers.length - 1)); pool.shutdown(); return result; }} Output: Time: 390ms Result: 50000005000000
Copy the code
Scheme 4: use parallel flow
public static void main(String[] args) { Instant start = Instant.now(); long result = LongStream.rangeClosed(0.10000000L).parallel().reduce(0, Long::sum); Instant end = Instant.now(); System.out.println("Time:" + Duration.between(start, end).toMillis() + "ms"); System.out.println("Results are:" + result); Output: Time: 130ms Result: 50000005000000
Copy the code
The parallel flow base is also a Fork/Join framework, but the task splitting is optimized.
Explanation in terms of time-consuming and efficiency: Fork/Join parallel flow, etc. Advantages can be realized only when the calculated numbers are very large. That said, parallel processing is not recommended if your calculations are small or not CPU-intensive.
Xiao Fei Fei: I don’t know. It seems not applicable. What are the advantages and disadvantages of using it?
Handsome small rice rice: the advantage is to make full use of the CPU, especially in the computationally intensive task, the disadvantage is that the task needs to be split, the good or bad of the split also means the good or bad of the task, for developers, or a little cost exists.
Xiao Fei: Well, yes, I have a resume here. There is an entry-level game developer who uses this technology. Please design relevant interview questions and ask him.
Handsome small rice rice: ok, that here probably design the following interview questions
- What is ForkJoin?
- What’s inside ForkJoin?
- Maybe talk about job theft?
- How to make the best use of the CPU to compute the sum of all integers in a very large array?
The answers to these interview questions are mostly in the topics we’re talking about.
Small fat fat: quite good, wait for promotion senior development you, weekend still study, right, sum up, will just chat record sum up, then later period do a share.
Handsome small rice rice: oh huo, can ah, first add some money ah, poor ah, I sum up
-
ForkJoinPool is particularly well suited to the implementation of divide-and-rule algorithms;
-
ForkJoinPool and ThreadPoolExecutor are complementary and do not replace each other.
-
ForkJoinTask has two core methods, fork() and join(), and three important subclasses, RecursiveAction, RecursiveTask, and CountedCompleter;
-
ForkjoinPool internal implementation based on “job stealing” algorithm;
-
Each thread has its own work queue, which is a double-ended queue. It accesses tasks from the queue head and other threads steal tasks from the tail.
-
ForkJoinPool is best for computation-intensive tasks, but ManagedBlocker can also be used for blocking tasks.
-
RecursiveTask can call fork() once less inside, using the current thread processing, which is a trick;