JDK Concurrency Tool for Java Concurrent Programming-Sharing Model 1: Thread Pool

Keywords: Java Concurrent Programming thread pool

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

Posted by mindevil on Wed, 08 Sep 2021 13:12:06 -0700