java thread pool knowledge summary.
Thread pool parameter meaning
- int corePoolSize
The number of core threads, that is, the number of worker threads under normal circumstances
- int maximumPoolSize,
maximum number of threads
- long keepAliveTime,
It needs to be understood in conjunction with the blocking queue: suppose the length of the blocking queue is 3, the number of cores is 2, and the maximum number of threads is 5. The runtime is like this: when the number of cores is greater than the number of cores, it will be placed in the blocking queue, and only if the queue is full New worker threads are enabled until the maximum number of threads is reached
When the maximum number of threads is reached, if the task submit ted to the thread pool becomes slower and the core thread can handle the work, the thread pool will dynamically reduce the number of worker threads to the number of core threads. The keepAliveTime here refers to the idle time of the threads other than the core thread. If it is larger than this parameter, the thread pool will recycle these threads
- TimeUnit unit,
the unit of the third parameter
- BlockingQueue<Runnable> workQueue,
Threads that exceed the number of core threads will be queued in the blocking queue. Do not use the Executors.newFixedThreadPool method to construct, the default size of the specified blocking queue (LinkedBlockingQueue) is Integer.MAX_VALUE, which needs to be specified explicitly
- ThreadFactory threadFactory,
Just use the default, distinguish the thread name if necessary
- RejectedExecutionHandler handler
Denial Policy:
AbortPolicy (default): throw exception directly
DiscardPolicy: Randomly discard
DiscardOldestPolicy: Discard the oldest thread
CallerRunsPolicy: Return execution to the caller thread.
Source code analysis
- Preliminary knowledge (bit operations)
- Original code (with sign bit): the highest bit is the sign bit, negative numbers are 1, positive numbers are 0
- Complement code: The original code except the sign bit, and the remaining bits are inverted.
- Two's complement (mainly used to represent negative numbers): Signed negative number's complement + 1, mainly to eliminate -0, only negative numbers are represented by complement. refer: https://www.zhihu.com/questio...
- How to save thread state
In the thread pool, the first three digits represent the running state, and the last 29 digits represent the number of worker threads.
// 29 private static final int COUNT_BITS = Integer.SIZE - 3; //1 (original code representation) left shift by 29 bits + (-1)'s complement (32 bits 1), the first three bits are 0, and the last 29 bits are 1 private static final int CAPACITY = (1 << COUNT_BITS) - 1; //Take the last 29 digits private static int workerCountOf(int c) { return c & CAPACITY; } //The negation of CAPACITY means that the first three bits are 1, and the last 29 bits are 0, so any AND operation with ~CAPACITY will only keep the first 3 bits. private static int runStateOf(int c) { return c & ~CAPACITY; }
- task management
submit The execution logic after submitting the task
int c = ctl.get(); //If the number of threads in the last 29 bits has not exceeded the number of cores, addWorker updates the number of threads (the last 29 bits of ctl) if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } //If the number of core threads is greater than the number of core threads, the status will be re-acquired. If the thread pool is still running, the task will be put into the blocking queue. if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); //If the thread pool is no longer running, reject it directly if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) //The first thread is empty and executes immediately, see the Worker class method addWorker(null, false); } //Call the addWorker method to update the number of ctl running threads else if (!addWorker(command, false)) reject(command);
- Worker thread management
In order to manage the life cycle of the Worker thread, the Worker thread is designed. It is worth mentioning that the Worker thread is an AQS, which will be locked before running. It will be unlocked after running.
Relying on this, when you want to interrupt these idle threads, you can judge whether the worker thread is running by whether it can be successfully locked. If the interrupt operation can be successfully locked, it means
The thread is no longer running, then you can call the interrupt method of the worker thread to interrupt the thread, and remove the worker thread reference in the hashSet. Wait for the jvm to recycle
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { //getTask gets runnable from blocking queue, and then runs while (task != null || (task = getTask()) != null) { // w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { //Execute queue tasks. task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { //If the task in the blocking queue is empty, execute the recycling logic //All worker thread references will be kept in a hashSet, processWorkerExit(w, completedAbruptly); } } //interrupt idle thread private void interruptIdleWorkers(boolean onlyOne) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { Thread t = w.thread; //If the worker thread is not interrupted and the lock is successful, the interrupt operation is performed if (!t.isInterrupted() && w.tryLock()) { try { t.interrupt(); } catch (SecurityException ignore) { } finally { w.unlock(); } } if (onlyOne) break; } } finally { mainLock.unlock(); } }
other
- How much is the thread pool?
From experience, it is necessary to distinguish whether it is cpu-intensive or io-intensive. If the cpu is intensive, in order to avoid context switching, the number should not be too much, generally the number of cpu cores + 1;
If it is io-intensive, it can be more, and it is necessary to roughly clarify the ratio of the time-consuming of io-type tasks to the time-consuming of cpu-intensive tasks.
- Deadlock coding and location
jstack troubleshooting
- ExecutorService.submit(Runnable task, T result) should be used like this
The result is used as a bridge between the child thread and the main thread, and is passed into the task as a construction parameter. The child thread can operate the result object.