Some of you may be unfamiliar with thread pools and even less familiar with how they work. So when they use threads, most of the time they use new threads to implement multithreading. However, good multithreading design is mostly implemented using thread pools.
Why use threads
- Reduce resource consumption. Reduces resource consumption for thread creation and destruction.
- Improved response time: thread creation time is T1, execution time is T2, and destruction time is T3, without T1 and T3
- Improve thread manageability
The thread pool is as follows: callers constantly submit tasks to the thread pool. A thread pool with a group of threads constantly fetching tasks from a queue is a typical producer-consumer model.
To implement a thread pool, there are several issues to consider:
- How long is the queue set? If it is unbounded, the caller keeps sending tasks to the queue, which may cause memory to run out. If it is bounded, what does the caller do when the queue is full?
- Is the number of threads in the thread pool fixed or dynamically changing?
- Each time a new task is submitted, is it queued? Let’s start a new thread
- Is the thread sleeping for a short period of time when there are no tasks? Or is it blocked? If blocked, how do I wake up?
In response to question 4, there are three approaches:
- No blocking queues, just normal thread-safe queues, and no blocking/wake up mechanism. When the queue is empty, threads in the thread pool can only sleep for a while and then wake up to see if new tasks are coming to the queue, and so on.
- No blocking queues are used, but a blocking/wake mechanism is implemented outside the queue and inside the thread pool
- Using blocking queues
Obviously, approach 3 is the best, avoiding both the hassle of implementing the blocking/wake mechanism within the thread pool itself and the resource consumption and latency associated with approach 1’s sleep/polling.
Let’s write a simple thread pool to give you a better understanding of how it works
Actual combat: handwriting easy thread pool
As you can see from the figure above, implementing a thread pool requires a blocking queue + a container for storing threads
/** * Five is working on customizing thread pools */
public class ThreadPool {
/** The number of threads in the default thread pool */
private static final int WORK_NUM = 5;
/** Number of tasks to process by default */
private static final int TASK_NUM = 100;
/** Save tasks */
private final BlockingQueue<Runnable> taskQueue;
private final Set<WorkThread> workThreads;// Save a collection of threads
private int workNumber;// Number of threads
private int taskNumber;// Number of tasks
public ThreadPool(a){
this(WORK_NUM , TASK_NUM);
}
public ThreadPool(int workNumber , int taskNumber) {
if (taskNumber<=0){
taskNumber = TASK_NUM;
}
if (workNumber<=0){
workNumber = WORK_NUM;
}
this.taskQueue = new ArrayBlockingQueue<Runnable>(taskNumber);
this.workNumber = workNumber;
this.taskNumber = taskNumber;
workThreads = new HashSet<>();
// The worker thread is ready
// Start a number of threads to get processing from the queue
for (int i=0; i<workNumber; i++) { WorkThread workThread =new WorkThread("thead_"+i); workThread.start(); workThreads.add(workThread); }}/** * Thread pools perform tasks by adding elements to BlockingQueue *@param task
*/
public void execute(Runnable task) {
try {
taskQueue.put(task);
} catch (InterruptedException e) {
// TODO Auto-generated catch blocke.printStackTrace(); }}/** * destroys the thread pool */
public void destroy(a){
System.out.println("ready close pool...");
for (WorkThread workThread : workThreads) {
workThread.stopWorker();
workThread = null;//help gc
}
workThreads.clear();
}
/** Inner class, implementation of worker thread */
private class WorkThread extends Thread{
public WorkThread(String name){
super(a); setName(name); }@Override
public void run(a) {
while(! interrupted()) {try {
Runnable runnable = taskQueue.take();// Get the task
if(runnable ! =null) {
System.out.println(getName()+" ready execute:"+runnable.toString());
runnable.run();// Execute the task
}
runnable = null;//help gc
} catch(Exception e) { interrupt(); e.printStackTrace(); }}}public void stopWorker(a){ interrupt(); }}}Copy the code
The code above defines the default number of threads and the default number of processing tasks, and the user can also customize the number of threads and processing tasks. Use BlockingQueue to block the queue for tasks. The benefits of using a set to store worker threads go without saying. Who knows all the answers
When a new object is constructed, the loop starts the thread and puts it into a set. WorkThread implements Thread, and run is easy to implement, because there is a stop method, so we need to check while, and then get the task from the taskQueue. Runnable.run (); Execute the task and then null the task
Destroying a thread simply iterates through the set, stopping each thread and turning it null
To execute the thread task execute, simply add the task from the block queue
Test it out:
public class TestMySelfThreadPool {
private static final int TASK_NUM = 50;// Number of tasks
public static void main(String[] args) {
ThreadPool myPool = new ThreadPool(3.50);
for (int i=0; i<TASK_NUM; i++) { myPool.execute(new MyTask("task_"+i)); }}static class MyTask implements Runnable{
private String name;
public MyTask(String name) {
this.name = name;
}
public String getName(a) {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void run(a) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("task :"+name+" end...");
}
@Override
public String toString(a) {
// TODO Auto-generated method stub
return "name = "+name; }}}Copy the code
The results ok. No problem