1. Thread Pool
1.Custom Thread Pool
Step 1: Customize the Denial Policy Interface
@FunctionalInterface // Thread pool rejection policy interface RejectPolicy<T> { void reject(BlockingQueue<T> queue, T task); }
Step 2: Customize the task queue
@Slf4j(topic = "c.BlockingQueue") class BlockingQueue<T> { // 1.Task Queue, Two-way Queue private final Deque<T> queue = new ArrayDeque<>(); // 2.lock private final ReentrantLock lock = new ReentrantLock(); // 3.Producer condition variable private final Condition fullWaitSet = lock.newCondition(); // 4.Consumer Conditional Variables private final Condition emptyWaitSet = lock.newCondition(); // 5.Queue capacity private final int capacity; public BlockingQueue(int capacity) { this.capacity = capacity; } // Capacity Size public int size() { lock.lock(); try { return this.capacity; } finally { lock.unlock(); } } // Blocking acquisition @Deprecated public T take() { lock.lock(); try { while (queue.isEmpty()) { try { emptyWaitSet.wait(); // Blocking Wait } catch (Exception e) { // Task interrupted e.printStackTrace(); } } T t = this.queue.removeFirst(); fullWaitSet.signal(); // awaken return t; } finally { lock.unlock(); } } // Blocking acquisition method with timeout public T poll(long timeout, TimeUnit unit) { lock.lock(); try { // Unify timeout into nanoseconds long nanos = unit.toNanos(timeout); while (queue.isEmpty()) { try { if (nanos <= 0) return null; // Returns the remaining time nanos = emptyWaitSet.awaitNanos(nanos); // Blocking Wait } catch (InterruptedException e) { e.printStackTrace(); } } T t = this.queue.removeFirst(); fullWaitSet.signal(); // awaken return t; } finally { lock.unlock(); } } // Blocking Add @Deprecated public void put(T element) { lock.lock(); try { while (queue.size() == this.capacity) { try { log.debug("Task Waiting to Join Task Queue: {}", element); fullWaitSet.await(); // Blocking Wait } catch (InterruptedException e) { e.printStackTrace(); } } queue.addLast(element); log.debug("Join Task Queue: {}", element); emptyWaitSet.signal(); // awaken } finally { lock.unlock(); } } // Blocking Add Method with Timeout public boolean offer(T task, long timeout, TimeUnit timeUnit) { lock.lock(); try { long nanos = timeUnit.toNanos(timeout); while (queue.size() == this.capacity) { try { if (nanos <= 0) { return false; } log.debug("Task Waiting to Join Task Queue: {}", task); nanos = fullWaitSet.awaitNanos(nanos);// Blocking Wait } catch (InterruptedException e) { e.printStackTrace(); } } queue.addLast(task); log.debug("Join Task Queue: {}", task); emptyWaitSet.signal(); // awaken return true; } finally { lock.unlock(); } } public void tryPut(RejectPolicy<T> rejectPolicy, T task) { lock.lock(); try { if (queue.size() == capacity) { // The queue is full rejectPolicy.reject(this, task); } else { // Free queue.addLast(task); log.debug("Join Task Queue: {}", task); emptyWaitSet.signal(); // awaken } } finally { lock.unlock(); } } }
Step 3: Customize the thread pool
@Slf4j(topic = "c.ThreadPool") class ThreadPool { // Task Queue private final BlockingQueue<Runnable> taskQueue; // Thread Collection private final HashSet<Worker> workers = new HashSet<>(); // Number of core threads in thread pool private final int coreSize; // Get the timeout for the task private final long timeout; private final TimeUnit timeUnit; private final RejectPolicy<Runnable> rejectPolicy; public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity, RejectPolicy<Runnable> rejectPolicy) { this.coreSize = coreSize; this.timeout = timeout; this.timeUnit = timeUnit; this.taskQueue = new BlockingQueue<>(queueCapacity); this.rejectPolicy = rejectPolicy; } // Execute Tasks public void execute(Runnable task) { // When the number of tasks does not exceed coreSize, hand them directly to the worker object for execution // Join task queue cache when number of tasks exceeds coreSize synchronized (workers) { if (workers.size() < this.coreSize) { Worker worker = new Worker(task); log.debug("Task direct execution: {}, Newly added worker: {}", task, worker); workers.add(worker); worker.start(); } else { // taskQueue.put(task); //wait stupidly for too long // Thread pool rejection policy // 1.wait stupidly for too long // 2.With timeout waiting // 3.Abandon Task Execution // 4.throw // 5.Let the caller execute the task himself // ... // Policy Mode taskQueue.tryPut(rejectPolicy, task); } } } class Worker extends Thread { private Runnable task; public Worker(Runnable task) { this.task = task; } @Override public void run() { // Execute Tasks // 1.Execute task when task is not null // 2.When the task is finished, get the task from the task queue and execute it /*while (task != null || (task = taskQueue.take()) != null) {*/ while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) { try { log.debug("worker: {}, Executing task: {}", this, task); task.run(); } catch (Exception e) { e.printStackTrace(); } finally { task = null; } } // Remove the current worker synchronized (workers) { workers.remove(this); log.debug("worker Removed: {}", this); } } } }
Step 4: Test
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; @Slf4j(topic = "c.TestPool") public class TestPool { public static void main(String[] args) { ThreadPool threadPool = new ThreadPool( 1, 1000, TimeUnit.MILLISECONDS, 1, (queue, task) -> { // Strategy 1.wait stupidly for too long // queue.put(task); // Policy 2, Wait with Timeout // queue.offer(task, 1500, TimeUnit.MILLISECONDS); // Policy 3, let caller abandon task execution // log.debug("Abandon Task Execution...{}, task);//(no logic) // Policy 4, let the caller throw the exception himself // throw new RuntimeException("Task execution failed..." + task); // Policy 5, let the caller execute the task himself task.run(); }); for (int i = 0; i < 4; i++) { String taskName = "task-" + i; threadPool.execute(() -> { log.debug("{}", taskName); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }); } } }
2. ThreadPoolExecutor
1) Thread pool state
ThreadPoolExecutor uses the upper 3 bits of int to represent the state of the thread pool and the lower 29 bits to represent the number of threads
Digital comparison, TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
This information is stored in an atomic variable, ctl, to combine the thread pool state with the number of threads so that a cas atomic operation can be used to assign values
// c is the old value and ctlOf returns the new value ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))); // rs is high 3 bits for thread pool state, wc is low 29 bits for number of threads, ctl is merging them private static int ctlOf(int rs, int wc) { return rs | wc; }
2) Construction methods
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
- Number of corePoolSize core threads (maximum number of threads reserved)
- Maximum PoolSize Maximum Threads
- keepAliveTime Lifetime - For Emergency Threads
- unit Time unit - For Emergency Threads
- workQueue blocking queue
- threadFactory Thread Factory - Can give a good name to a thread when it is created
- handler rejection policy
Operation mode:
- There are no threads in the thread pool at first. When a task is committed to the thread pool, the thread pool creates a new thread to perform the task.
- When the number of threads reaches corePoolSize and no threads are idle, adding tasks will queue up to the workQueue queue until there are idle threads.
- If a bounded queue is selected for the queue, then when the task exceeds the queue size, a maximum umPoolSize - corePoolSize number of threads are created to rescue the emergency (emergency threads).
- A rejection policy is executed when a thread reaches maximumPoolSize with new tasks. The rejection policy jdk provides four implementations (the first four below) and other well-known frameworks provide implementations
- AbortPolicy causes the caller to throw a RejectedExecutionException exception, which is the default policy
- CallerRunsPolicy lets the caller run the task
- DiscardPolicy abandoned this task
- DiscardOldestPolicy discards the earliest task in the queue and replaces it
- An implementation of Dubbo that logs and dump thread stack information before throwing a RejectedExecutionException exception to facilitate problem location
- Netty's implementation is to create a new thread to perform the task
- The implementation of ActiveMQ with timeout waiting (60s) attempts to queue, similar to our previously customized denial policy
- The implementation of PinPoint, which uses a chain of rejection policies and tries each rejection policy in the chain one by one
- When the peak is over, rescue threads beyond corePoolSize need to end saving resources if they don't have tasks to do for a while, which is controlled by keepAliveTime and unit.
Based on this construction method, many factory methods are provided in the JDK Executors class to create thread pools for various purposes:
- newFixedThreadPool: Fixed-size thread pool
- newCachedThreadPool: With Cached Thread Pool
- newSingleThreadExecutor: Single Thread Thread Pool
3) newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
Characteristic
- Number of core threads = = Maximum number of threads (no rescue threads were created), so no timeout is required
- Blocking queues are unbounded and can accommodate any number of tasks
Evaluation is appropriate for tasks that are known to be relatively time consuming
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @Slf4j(topic = "c.TestThreadPoolExecutors") public class TestThreadPoolExecutors { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(2); pool.execute(() -> log.debug("task1")); pool.execute(() -> log.debug("task2")); pool.execute(() -> log.debug("task3")); pool.execute(() -> log.debug("task4")); pool.execute(() -> log.debug("task5")); } }
Custom Thread Factory:
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; @Slf4j(topic = "c.TestThreadPoolExecutors") public class TestThreadPoolExecutors { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(2, // Custom Thread Factory new ThreadFactory() { private final AtomicInteger t = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { return new Thread(r, "myPool-t-" + t.getAndIncrement()); } } ); pool.execute(() -> log.debug("task1")); pool.execute(() -> log.debug("task2")); pool.execute(() -> log.debug("task3")); pool.execute(() -> log.debug("task4")); pool.execute(() -> log.debug("task5")); } }
4) newCachedThreadPool
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
Characteristic
- The number of core threads is 0, the maximum number of threads is Integer.MAX_VALUE, and the idle lifetime of the rescue thread is 60s, which means
- All are emergency threads (recyclable after 60s)
- Emergency threads can be created indefinitely
- The queue uses the SynchronousQueue implementation, which is characterized by no capacity and no threads to fetch (one-hand payment, one-hand delivery)
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; @Slf4j(topic = "c.TestSynchronousQueue") public class TestSynchronousQueue { public static void main(String[] args) throws InterruptedException { SynchronousQueue<Integer> integers = new SynchronousQueue<>(); new Thread(() -> { try { log.debug("putting {} ", 1); integers.put(1); // block log.debug("{} putted...", 1); log.debug("putting {} ", 2); integers.put(2); log.debug("{} putted...", 2); } catch (InterruptedException e) { e.printStackTrace(); } }, "t1").start(); TimeUnit.SECONDS.sleep(1); new Thread(() -> { try { log.debug("taking {}", 1); Integer i = integers.take(); // End Blocking System.out.println("i: " + i); } catch (InterruptedException e) { e.printStackTrace(); } }, "t2").start(); TimeUnit.SECONDS.sleep(1); new Thread(() -> { try { log.debug("taking {}", 2); Integer i = integers.take(); System.out.println("i: " + i); } catch (InterruptedException e) { e.printStackTrace(); } }, "t3").start(); } }
output
Evaluate the entire thread pool as the number of threads increases according to the number of tasks, with no upper limit. Release threads after 1 minute of idle time when tasks are completed. Suitable for situations where tasks are dense but each task takes less time to execute
5) newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
Use scenarios:
Multiple tasks are expected to be queued. When the number of threads is fixed to 1 and the number of tasks is more than 1, an unbounded queue is placed. When the task is finished, the only thread will not be released.
Difference:
- Create a single-threaded serial execution task yourself. If the task fails and terminates, there is no remedy. The thread pool also creates a new thread to ensure that the pool works properly.
- Executors.newSingleThreadExecutor() always has 1 number of threads and cannot be modified
- FinalizableDelegatedExecutorService applies the decorator mode, exposing only the ExecutorService interface to the outside world, so it is not possible to invoke a method unique to ThreadPoolExecutor
- Executors.newFixedThreadPool(1) was initially 1 and can be modified later
- Exposed is the ThreadPoolExecutor object, which can be modified by calling setCorePoolSize and other methods after forcing
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @Slf4j(topic = "c.TestExecutors") public class TestExecutors { public static void main(String[] args) { test2(); } private static void test2() { ExecutorService pool = Executors.newSingleThreadExecutor(); pool.execute(() -> { log.debug("task: 1"); int i = 1 / 0; }); pool.execute(() -> log.debug("task: 2")); pool.execute(() -> log.debug("task: 3")); } }
6) Submit Tasks
// Execute Tasks void execute(Runnable command); // Submit task and use return value Future to get task execution results <T> Future<T> submit(Callable<T> task); // Submit all tasks in tasks <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException; // Submit all tasks in tasks with timeout <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException; // Submit all tasks in tasks, which task completes successfully first, returns the results of this task execution, and cancels other tasks <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; // Submit all tasks in tasks, which task completes successfully first, returns the results of this task execution, cancels other tasks, and has a timeout <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
submit chestnuts:
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.*; @Slf4j(topic = "c.TestSubmit") public class TestSubmit { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); Future<String> future = pool.submit(new Callable<String>() { @Override public String call() throws Exception { log.debug("running..."); TimeUnit.SECONDS.sleep(2); log.debug("create a result!"); return "one fine"; } }); String result = future.get(); // block log.debug("result: {}", result); } }
invokeAll chestnuts:
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; import java.util.List; import java.util.concurrent.*; @Slf4j(topic = "c.TestInvokeAll") public class TestInvokeAll { public static void main(String[] args) throws InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); List<Future<String>> futures = pool.invokeAll(Arrays.asList( () -> { log.debug("begin-1"); TimeUnit.SECONDS.sleep(1); return "1"; }, () -> { log.debug("begin-2"); TimeUnit.SECONDS.sleep(2); return "2"; }, () -> { log.debug("begin-3"); TimeUnit.SECONDS.sleep(2); return "3"; } )); futures.forEach(f -> { try { String result = f.get(); log.debug("result: " + result); } catch (Exception e) { e.printStackTrace(); } }); } }
invokeAny chestnuts:
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; import java.util.concurrent.*; @Slf4j(topic = "c.TestInvokeAny") public class TestInvokeAny { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(1); String result = pool.invokeAny(Arrays.asList( () -> { log.debug("begin-1"); TimeUnit.SECONDS.sleep(1); log.debug("end-1"); return "1"; }, () -> { log.debug("begin-2"); TimeUnit.SECONDS.sleep(2); log.debug("end-2"); return "2"; }, () -> { log.debug("begin-3"); TimeUnit.SECONDS.sleep(2); log.debug("end-3"); return "3"; } )); log.debug("{}", result); } }
7) Close thread pool
shutdown
Definition:
/* Thread pool state changed to SHUTDOWN - No new tasks will be received - Submitted tasks will be completed - This method does not block the execution of the calling thread */ void shutdown();
Realization:
public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); // Modify thread pool state advanceRunState(SHUTDOWN); // Only idle threads will be interrupted interruptIdleWorkers(); onShutdown(); // Extension Point ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } // Attempt to terminate (threads that are not running can terminate immediately, and if there are running threads, will not wait) tryTerminate(); }
Chestnuts:
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @Slf4j(topic = "c.TestShutDown") public class TestShutDown { public static void main(String[] args) throws InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); Future<Integer> result1 = pool.submit(() -> { log.debug("task 1 running..."); TimeUnit.SECONDS.sleep(1); log.debug("task 1 finish!"); return 1; }); Future<Integer> result2 = pool.submit(() -> { log.debug("task 2 running..."); TimeUnit.SECONDS.sleep(1); log.debug("task 2 finish!"); return 2; }); Future<Integer> result3 = pool.submit(() -> { log.debug("task 3 running..."); TimeUnit.SECONDS.sleep(4); log.debug("task 3 finish!"); return 1; }); log.debug("shutdown start..."); pool.shutdown(); log.debug("shutdown end!"); boolean b = pool.awaitTermination(3, TimeUnit.SECONDS); // Blocking 3s log.debug("Blocking End..."); // java.util.concurrent.RejectedExecutionException // Future<Integer> result4 = pool.submit(() -> { // log.debug("task 4 running..."); // TimeUnit.SECONDS.sleep(1); // log.debug("task 4 finish!"); // return 1; // }); } }
shutdownNow
Definition:
/* Thread pool state changed to STOP - No new tasks will be received - Returns the tasks in the queue - And interrupt the executing task */ List<Runnable> shutdownNow();
Realization:
public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); // Modify thread pool state advanceRunState(STOP); // Interrupt all threads interruptWorkers(); // Get remaining tasks in the queue tasks = drainQueue(); } finally { mainLock.unlock(); } // Attempt to End tryTerminate(); return tasks; }
Chestnuts:
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @Slf4j(topic = "c.TestShutDown") public class TestShutDown { public static void main(String[] args) throws InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(2); Future<Integer> result1 = pool.submit(() -> { log.debug("task 1 running..."); TimeUnit.MILLISECONDS.sleep(400); log.debug("task 1 finish!"); return 1; }); Future<Integer> result2 = pool.submit(() -> { log.debug("task 2 running..."); TimeUnit.SECONDS.sleep(2); log.debug("task 2 finish!"); return 2; }); Future<Integer> result3 = pool.submit(() -> { log.debug("task 3 running..."); TimeUnit.SECONDS.sleep(4); log.debug("task 3 finish!"); return 3; }); Future<Integer> result4 = pool.submit(() -> { log.debug("task 4 running..."); TimeUnit.SECONDS.sleep(4); log.debug("task 4 finish!"); return 4; }); TimeUnit.MILLISECONDS.sleep(500); log.debug("shutdownNow start..."); List<Runnable> runnables = pool.shutdownNow(); // Tasks not performed, task4 here log.debug("shutdownNow end!"); log.debug("runnables: {}", runnables); } }
Other methods
// This method returns true if the thread pool is not in RUNNING state boolean isShutdown(); // Is the thread pool state TERMINATED boolean isTerminated(); // Since the calling thread does not wait for all tasks to run after shutdown is called, it can use this method to wait if it wants to do something after the thread pool TERMINATED boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
*Worker Thread-worker thread for mode
1.Definition
Allow limited worker threads (Worker Thread) to process an infinite number of tasks asynchronously in turn. It can also be categorized as a division of labor mode, which typically implements a thread pool and also reflects the hedonic mode in the classic design mode.
For example, a salvage server (thread), which takes turns processing each guest's order (task), is too costly if each guest is assigned a dedicated server (compared to another multithreaded design pattern: Thread-Per-Message)
Note that different task types should use different thread pools to avoid hunger and increase efficiency
For example, if a restaurant's workers are both entertaining guests (Task Type A) and cooking at the back of the kitchen (Task Type B) which is obviously inefficient, it would be more reasonable to divide the restaurant workers into waiters (Thread Pool A) and cooks (Thread Pool B), of course you can think of a more detailed division of work
2.hunger
Fixed-size thread pools are hungry
- Two workers are two threads in the same thread pool
- What they do is order meals for their guests and cook at the back of the kitchen, which is a two-stage task
- Guest order: must finish ordering, wait for the food to be ready, serve, during which time the worker handling the order must wait
- Chef's cooking: Nothing to do, just do it
- For example, worker A handles the ordering task. Next, it waits for worker B to prepare the dish and serve it. They also work well together.
- But now there are two guests coming at the same time. At this time, both workers A and B go to process the meal. No one cooks, no one is hungry.
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @Slf4j(topic = "c.TestStarvation") public class TestStarvation { static final List<String> MENU = Arrays.asList("Sauteed Potato, Green Pepper and Eggplant", "Kung Pao Chicken", "Sauteed Chicken Dices with Chili Peppers", "Roast chicken wings"); static Random RANDOM = new Random(); static String cooking() { return MENU.get(RANDOM.nextInt(MENU.size())); } public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.execute(() -> { log.debug("Processing Orders..."); Future<String> f = executorService.submit(() -> { log.debug("Cook a dish"); return cooking(); }); try { log.debug("Serve: {}", f.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); /*executorService.execute(() -> { log.debug("Processing meals... "; Future<String> f = executorService.submit(() -> { log.debug(""Cooking"; return cooking(); }); try { log.debug("Serving: {} ", f.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } });*/ } }
output
When the comment is cancelled, possible output (note: here is hunger, not deadlock)
Solutions can increase the size of the thread pool, but they are not the root solution or, as mentioned earlier, use different thread pools for different task types, such as:
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @Slf4j(topic = "c.TestDeadLock") public class TestDeadLock { static final List<String> MENU = Arrays.asList("Sauteed Potato, Green Pepper and Eggplant", "Kung Pao Chicken", "Sauteed Chicken Dices with Chili Peppers", "Roast chicken wings"); static Random RANDOM = new Random(); static String cooking() { return MENU.get(RANDOM.nextInt(MENU.size())); } public static void main(String[] args) { ExecutorService waiterPool = Executors.newFixedThreadPool(1); ExecutorService cookPool = Executors.newFixedThreadPool(1); waiterPool.execute(() -> { log.debug("Processing Orders..."); Future<String> f = cookPool.submit(() -> { log.debug("Cook a dish"); return cooking(); }); try { log.debug("Serve: {}", f.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); waiterPool.execute(() -> { log.debug("Processing Orders..."); Future<String> f = cookPool.submit(() -> { log.debug("Cook a dish"); return cooking(); }); try { log.debug("Serve: {}", f.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }); } }
output
3. How many thread pools are appropriate to create
- Too small can cause programs to underutilize system resources and be prone to starvation
- Over-assembly results in more thread context switching and more memory usage
3.1 CPU-intensive operations
Optimal CPU utilization is usually achieved with CPU core count + 1, which ensures that when a thread is paused due to page loss failures (operating system) or other causes, the extra thread will be able to top it, ensuring that the CPU clock cycle is not wasted
3.2 I/O intensive operations
CPU is not always busy, for example, when you perform business calculations, CPU resources are used, but when you perform I/O operations, remote RPC calls, including database operations, the CPU is idle and you can use multithreading to increase its utilization.
The empirical formula is as follows
Number of threads = Number of Kernels * Expect CPU Utilization rate * Total Time(CPU computing time+waiting time) / CPU computing time
For example, 4-core CPU calculation time is 50%, other wait time is 50%, expect 100% utilization of CPU, apply Formula
4 * 100% * 100% / 50% = 8
For example, 4-core CPU calculation time is 10%, other wait time is 90%, expect 100% utilization of CPU, apply Formula
4 * 100% * 100% / 10% = 40
4.Custom Thread Pool
8) Task Scheduling Thread Pool
In Task Scheduling Thread PoolBefore the function is added, the timer function can be implemented using java.util.Timer. The advantage of Timer is that it is simple and easy to use, but because all tasks are scheduled by the same thread, all tasks are executed serially and only one task can be executed at the same time. The delay or exception of the previous task will affect the later tasks.
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; @Slf4j(topic = "c.TestTimer") public class TestTimer { public static void main(String[] args) { Timer timer = new Timer(); TimerTask task1 = new TimerTask() { @Override public void run() { log.debug("task 1"); // int i = 1 / 0; try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } }; TimerTask task2 = new TimerTask() { @Override public void run() { log.debug("task 2"); } }; log.debug("main start..."); // Use timer to add two tasks that you want to execute after 1s // However, since there is only one thread in the timer to execute the tasks in the queue sequentially, the delay in Task 1 affects the execution of Task 2 timer.schedule(task1, 1000); timer.schedule(task2, 1000); } }
output
Override with ScheduledExecutorService: - Delay task execution
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @Slf4j(topic = "c.TestScheduledExecutorService") public class TestScheduledExecutorService { public static void main(String[] args) { ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); log.debug("main start..."); // Add two tasks that you want to execute in 1s executor.schedule(() -> { log.debug("task1 start..."); // int i = 1 / 0; try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }, 1000, TimeUnit.MILLISECONDS); executor.schedule(() -> { log.debug("task2 start..."); }, 1000, TimeUnit.MILLISECONDS); } }
output
SchduleAtFixedRate example: - Perform tasks on a regular basis
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @Slf4j(topic = "c.ScheduleAtFixedRateTest") public class ScheduleAtFixedRateTest { public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); log.debug("main start..."); // delay pool.scheduleAtFixedRate(() -> { log.debug("running..."); }, 1, 1, TimeUnit.SECONDS); } }
output
SchduleAtFixedRate example (task execution time exceeds interval time):
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @Slf4j(topic = "c.ScheduleAtFixedRateTest") public class ScheduleAtFixedRateTest { public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); log.debug("main start..."); // delay pool.scheduleAtFixedRate(() -> { log.debug("running..."); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }, 1, 1, TimeUnit.SECONDS); // delay starts at the end of the last task /*pool.scheduleWithFixedDelay(() -> { log.debug("running..."); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }, 1, 1, TimeUnit.SECONDS);*/ } }
Output analysis: Start with a delay of 1 s, then, due to task execution time > interval, interval is "supported" to 2 s
SchduleWithFixedDelay example:
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @Slf4j(topic = "c.ScheduleAtFixedRateTest") public class ScheduleAtFixedRateTest { public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); log.debug("main start..."); // delay /*pool.scheduleAtFixedRate(() -> { log.debug("running..."); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }, 1, 1, TimeUnit.SECONDS);*/ // delay starts at the end of the last task pool.scheduleWithFixedDelay(() -> { log.debug("running..."); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }, 1, 1, TimeUnit.SECONDS); } }
Output analysis: Initially, with a delay of 1s, the scheduleWithFixedDelay interval is the end of the previous task <->the delay <->the start of the next task so the intervals are all 3s
The entire thread pool is evaluated as having a fixed number of threads and queued in an unbounded queue when there are more tasks than threads. When tasks are executed, they are not released. Tasks used to perform delayed or repetitive tasks
9) Correct handling of task execution exceptions
Method 1: Catch abnormalities actively
private static void test1() { ExecutorService pool = Executors.newFixedThreadPool(1); pool.submit(() -> { try { log.debug("task1"); int i = 1 / 0; } catch (Exception e) { log.error("error:", e); } }); }
output
Method 2: Use Future
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @Slf4j(topic = "c.TestScheduleException") public class TestScheduleException { public static void main(String[] args) { // test1(); test2(); } private static void test2() { ExecutorService pool = Executors.newFixedThreadPool(1); Future<Boolean> f = pool.submit(() -> { log.debug("task1"); int i = 1 / 0; return true; }); Boolean result = null; try { result = f.get(); } catch (Exception e) { log.error("error: {}", e.getMessage()); } log.debug("result:{}", result); } private static void test1() { ExecutorService pool = Executors.newFixedThreadPool(1); pool.submit(() -> { try { log.debug("task1"); int i = 1 / 0; } catch (Exception e) { log.error("error: {}", e.getMessage()); } }); } }
output
*Timed Tasks Applied
Periodic execution
How do I get my tasks to be performed at 18:00:00 a.m. every Thursday?
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.time.DayOfWeek; import java.time.Duration; import java.time.LocalDateTime; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @Slf4j(topic = "c.TestSchedule") public class TestSchedule { public static void main(String[] args) { // Get the current time LocalDateTime now = LocalDateTime.now(); // Get Thursday at 18:00:00.000 LocalDateTime thursday = now.with(DayOfWeek.THURSDAY) // THURSDAY .withHour(18).withMinute(0).withSecond(0).withNano(0); // If the current time has exceeded 18:00:00.000 this Thursday, look for 18:00:00.000 next Thursday if (now.compareTo(thursday) >= 0) { thursday = thursday.plusWeeks(1); // Next Thursday } // Calculate time difference, i.e. delayed execution time long initialDelay = Duration.between(now, thursday).toMillis(); // Calculate the interval, that is, milliseconds in a week long oneWeek = 7 * 24 * 3600 * 1000; ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); System.out.println("Start time:" + new Date()); executor.scheduleAtFixedRate(() -> { System.out.println("Execution time:" + new Date()); }, initialDelay, oneWeek, TimeUnit.MILLISECONDS); } }
10) Tomcat Thread Pool
Where does Tomcat use the thread pool?
- LimitLatch is used to limit the flow and can control the maximum number of connections, similar to Semaphore in J.U.C.
- Acceptor is only responsible for [receiving new socket connections]
- Poller is only responsible for listening for socket channel s [readable I/O events]
- Once readable, encapsulate a task object (socketProcessor) and submit it to the Executor thread pool for processing
- The worker threads in the Executor thread pool are ultimately responsible for processing requests
Tomcat thread pool extends ThreadPoolExecutor with slightly different behavior
- If the bus number reaches maximumPoolSize
- The RejectedExecutionException exception is not thrown immediately
- Instead, try to queue the task again and throw the RejectedExecutionException exception if it fails
Source tomcat-7.0.42
public void execute(Runnable command, long timeout, TimeUnit unit) { submittedCount.incrementAndGet(); try { super.execute(command); } catch (RejectedExecutionException rx) { if (super.getQueue() instanceof TaskQueue) { final TaskQueue queue = (TaskQueue)super.getQueue(); try { if (!queue.force(command, timeout, unit)) { submittedCount.decrementAndGet(); throw new RejectedExecutionException("Queue capacity is full."); } } catch (InterruptedException x) { submittedCount.decrementAndGet(); Thread.interrupted(); throw new RejectedExecutionException(x); } } else { submittedCount.decrementAndGet(); throw rx; } } }
TaskQueue.java
public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException { if ( parent.isShutdown() ) throw new RejectedExecutionException( "Executor not running, can't force a command into the queue" ); return super.offer(o,timeout,unit); //forces the item onto the queue, to be used if the task is rejected }
Connector Configuration
Executor Thread Configuration
3. Fork/Join
1) Concepts
Fork/Join is a new thread pool implementation joined by JDK 1.7, which represents a divide-and-conquer idea and is suitable for cpu-intensive operations that can split tasks
Task splitting is splitting a large task into algorithmically identical small tasks until it cannot be split and solved directly. Recursion-related calculations, such as merge ordering, Fibonacci series, can be solved using the idea of division
Fork/Join adds multi-threading on the basis of partitioning, which allows each task to be decomposed and merged into different threads to complete, further improving computational efficiency
Fork/Join creates a thread pool of the same size as the cpu core by default
2) Use
Tasks submitted to the Fork/Join thread pool need to inherit RecursiveTask (with return value) or RecursiveAction (without return value), for example, a task that sums up integers between 1 and N is defined below
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; @Slf4j(topic = "c.TestForkJoin2") public class TestForkJoin { public static void main(String[] args) { ForkJoinPool pool = new ForkJoinPool(4); Integer result = pool.invoke(new MyTask(5)); log.debug("result: {}", result); } } // Find the sum of integers between 1-n @Slf4j(topic = "c.MyTask") class MyTask extends RecursiveTask<Integer> { private final int n; public MyTask(int n) { this.n = n; } @Override public String toString() { return "{" + n + '}'; } // Do task splitting logic @Override protected Integer compute() { // Termination conditions;If n is already 1, you can get the result if (n == 1) { log.debug("join() {}", n); return n; } // Split the task (fork) MyTask t1 = new MyTask(n - 1); t1.fork(); // Let the thread perform this task log.debug("fork(): {} + {}", n, t1); // Join result int result = n + t1.join(); // Add current results to get task results log.debug("join(): {} + {} = {}", n, t1, result); return result; } }
Result
Graph representation
Improvement
package top.onefine.test.c8; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; @Slf4j(topic = "c.TestForkJoin2") public class TestForkJoin2 { public static void main(String[] args) { ForkJoinPool pool = new ForkJoinPool(4); Integer result = pool.invoke(new MyTask2(1, 5)); log.debug("result: {}", result); } } // Find the sum of integers between 1-n @Slf4j(topic = "c.MyTask2") class MyTask2 extends RecursiveTask<Integer> { int begin; int end; public MyTask2(int begin, int end) { this.begin = begin; this.end = end; } @Override public String toString() { return "{" + begin + "," + end + '}'; } @Override protected Integer compute() { // 5, 5 if (begin == end) { log.debug("join() {}", begin); return begin; } // 4, 5 if (end - begin == 1) { log.debug("join() {} + {} = {}", begin, end, end + begin); return end + begin; } // 1 5 int mid = (end + begin) / 2; // 3 MyTask2 t1 = new MyTask2(begin, mid); // 1,3 t1.fork(); MyTask2 t2 = new MyTask2(mid + 1, end); // 4,5 t2.fork(); log.debug("fork() {} + {} = ?", t1, t2); int result = t1.join() + t2.join(); log.debug("join() {} + {} = {}", t1, t2, result); return result; } }
Result
Graph representation