Scene Review
Today I went back to my dormitory and singed: Cut me out soon ~, which frightened me to get to know the situation quickly. I thought I was having trouble with my girlfriend. As a result, the interview was abused by the interviewer again today, so I had to delete the interviewer.
Interviewer: I see that I have written concurrent programming proficiency in my resume. Thread pools must be used in my usual work. What scenarios do you usually use?
Lobule: Well, thread pools are used normally. I use them in crawl scenarios where multiple network requests can be processed in parallel through thread pools, which can improve the throughput of the system.
Interviewer: Well, thread pools are a common scenario for crawlers. How did you create them?
Leaf: I usually use the method provided by JDK's own factory method to create thread pools directly. I don't think it's a big name.
Interviewer: You are talking about the Executors class. Usually we use a single-threaded thread pool, a fixed-threaded thread pool, and so on. How does it create different thread pools with parameters at the bottom?
Lobule: emm... You are saying ThreadPoolExecutor. It seems to consist of seven parameters, such as number of core threads, maximum threads, length of idle time, unit of idle time, task queue, thread factory, rejection policy.
Interviewer: Well, it looks like you still know a little about thread pools. If I now have 10 core threads, 20 maximum threads, and an unlimited queue of tasks, and now I have 30 tasks at once, how many threads are in the current thread pool, will the tasks be rejected?
Lobule: This is simple. The maximum number of threads is 20 and there are 30 tasks coming at once. Of course, the number of threads is 20, because the task queue is unbounded, the task will not be rejected.
Interviewer: Well, if I change the task queue to a size of 20, how many requests can I receive at most now?
Lobule: emm... Should be core threads + maximum threads + task queue size = 50.
Interviewer: Today's interview is here first.
Through the miserable interview with Lobule, it is not difficult to find that the thread pool is a question that is frequently asked by the interviewer. A few simple parameters of the thread pool may not be the result we expected. And we may call the simple methods provided by the Executors class directly for the sake of convenience during normal encoding, but there may be some problems that need to be considered. All task queues in the thread pool methods provided in the Executors class are unbounded, which may cause memory overflow. And I want to use other task rejection strategies, how do we do that? With these questions in mind, here is a small strip that takes you from shallow to deep.
Overall structure
From the diagram above, you can see the overall inheritance relationship of ThreadPoolExecutor, where you can see the author of the design thread pool, designed two interfaces separated by their responsibilities, and added hook functions through a template approach.
Executor interface
public interface Executor { // How a task is executed depends on how the subclasses are implemented void execute(Runnable command); }
ExecutorService interface
public interface ExecutorService extends Executor { // Stop the thread pool until all tasks in the pool have been executed void shutdown(); // Stop the thread pool immediately and return unexecuted tasks List<Runnable> shutdownNow(); // Stop state boolean isShutdown(); // End or not boolean isTerminated(); // Waiting for thread pool to stop boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; // Submit a task with a return result and return a proxy object for the result <T> Future<T> submit(Callable<T> task); // Submit task without return, task execution successfully returns result <T> Future<T> submit(Runnable task, T result); // Submit tasks that do not return Future<?> submit(Runnable task); // Completes all tasks in the tasks collection and returns the results <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException; // Add timeout based on previous method <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException; // Return once any task in tasks has been executed <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; // Timeout added in previous step <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
ExecutorService describes that a thread pool should have functions such as closing the thread pool, submitting tasks, task execution policies, and so on.
Next, we'll look at how thread pools reduce code through template methods using the AbstractExecutorService Abstract class.
The structure diagram of the class shows that the abstract class implements the methods of submit, invokeAll, invokeAny.
submit
public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); // NewTaskForThis method is not covered here, it can be understood as wrapping the task RunnableFuture<Void> ftask = newTaskFor(task, null); // Call methods in Executor class execute(ftask); return ftask; }
The source code shows that this method wraps the task and calls the execute method directly.
invokeAll
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException { if (tasks == null) throw new NullPointerException(); // Calculate timeout long nanos = unit.toNanos(timeout); // Collection for task storage ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size()); boolean done = false; try { for (Callable<T> t : tasks) futures.add(newTaskFor(t)); // Calculate Task Execution Deadline final long deadline = System.nanoTime() + nanos; final int size = futures.size(); // task in the execution collection of a loop for (int i = 0; i < size; i++) { execute((Runnable)futures.get(i)); // Determine whether the deadline has been reached nanos = deadline - System.nanoTime(); if (nanos <= 0L) return futures; } // The code runs here, indicating that all tasks have been executed within the specified time, but the processing results are unknown for (int i = 0; i < size; i++) { Future<T> f = futures.get(i); // Get the return value agent of the task to determine if it is completed if (!f.isDone()) { // Return to the future collection if the deadline has been reached if (nanos <= 0L) return futures; try { // Block waiting for future to return f.get(nanos, TimeUnit.NANOSECONDS); } catch (CancellationException ignore) { } catch (ExecutionException ignore) { } catch (TimeoutException toe) { // Return future s collection if timed out return futures; } // Update Remaining Time nanos = deadline - System.nanoTime(); } } // The code runs here to indicate that all tasks have been performed and to set the completion flag done = true; return futures; } finally { /** Cancel all tasks in progress if don is false Only a time-out during task submission and a time-out to get an execution result will be false **/ if (!done) for (int i = 0, size = futures.size(); i < size; i++) futures.get(i).cancel(true); } }
invokeAny
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks, boolean timed, long nanos) throws InterruptedException, ExecutionException, TimeoutException { // Empty check if (tasks == null) throw new NullPointerException(); int ntasks = tasks.size(); if (ntasks == 0) throw new IllegalArgumentException(); ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks); /** This class is read outside the JUC package and provides an overview of what this class provides. The thread pool instance is passed to this class so that it can call the execute method of the thread pool without having to maintain the pool itself. This class implements submit submission tasks and returns furure, and maintains a blocking queue internally where the future object is placed when the task executes successfully take Method is get element of queue blocking poll Method is a non-blocking get element of the queue **/ ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this); try { // Record exceptions ExecutionException ee = null; // Calculate timeout if on timeout final long deadline = timed ? System.nanoTime() + nanos : 0L; Iterator<? extends Callable<T>> it = tasks.iterator(); // Will be the first task futures.add(ecs.submit(it.next())); // Number of tasks-1 --ntasks; int active = 1; for (;;) { // Non-blocking Get Element Future<T> f = ecs.poll(); if (f == null) { // If there are still tasks to execute at this time, continue committing them and add activity + 1 if (ntasks > 0) { --ntasks; futures.add(ecs.submit(it.next())); ++active; } // The active number is 0 to exit the cycle, and the active number is 0 to move from future.get() Gets the throw exception to execute here else if (active == 0) break; else if (timed) { // If timeout is on, wait in response queue for element to be fetched, timeout error f = ecs.poll(nanos, TimeUnit.NANOSECONDS); if (f == null) throw new TimeoutException(); nanos = deadline - System.nanoTime(); } else // Unopened timeout enters an infinite waiting block to get a response f = ecs.take(); } // If the response queue is not empty, a task is completed at this time if (f != null) { // Active count-1 --active; /** Get response results through future, if returned directly If the get result fails to record an exception, continue traversing to get the next successful result from the response queue **/ try { return f.get(); } catch (ExecutionException eex) { ee = eex; } catch (RuntimeException rex) { ee = new ExecutionException(rex); } } } // Executing here means that all tasks are completed and all future. Getts fail, exceptions will be thrown if (ee == null) ee = new ExecutionException(); throw ee; } finally { // Finally, undo all tasks once for (int i = 0, size = futures.size(); i < size; i++) futures.get(i).cancel(true); } }
Final conclusion
Readers who can read this are confident that you have mastered a very important method of base classes in the thread pool. At least you know how to increase code reuse by designing base classes, and invokeAny logic execution is interesting. Reading source code is a tedious process, but your thinking evolves with the big guys step by step. That's why Internet companies have asked to read the source code. In the next post, I'll officially enter the ThreadPoolExecutor class to unravel its mystery and the interviewer's questions will be answered.
This article is published by blog OpenWrite Release!