In-depth thinking about thread pool face-to-face II: jdk thread pool design Q&A

2 questions

2.1 Here "only when the thread pool is unbounded or can reject tasks, the queue has practical value", why do you say that

SynchronousQueue is a special queue that does not keep tasks, but directly hands them over to worker threads. This kind of queue is suitable for executing a large number of asynchronous tasks with very short life cycles.

When saying "this queue is only of real value if the thread pool is unbounded or can reject tasks" because:

Unbounded thread pool: SynchronousQueue has no ability to store tasks, so each time a task is added, an available thread is required. If the thread pool is unbounded, then each time a task is added, a new thread can be created to handle the task.

Rejection strategy: If the thread pool has reached its maximum and all threads are busy, using SynchronousQueue will not be able to store more tasks. At this time, the thread pool must have a strategy to reject new tasks, otherwise there will be a problem of resource exhaustion.

Scenarios using SynchronousQueue, such as Executors.newCachedThreadPool, are based on the principle that it does not keep any pending tasks, but creates new threads as needed until the maximum limit of the system is reached. When a thread is idle for a certain period of time, it will be terminated and recycled, so this thread pool is suitable for executing a large number of asynchronous tasks with a very short life cycle.

2.2 Is the maxPoolSize parameter of the thread pool dynamically variable?

In Java java.util.concurrent.ThreadPoolExecutor, maxPoolSizethe parameters are set when the thread pool is initialized, but can also be dynamically modified after creation.

You can use ThreadPoolExecutormethods setMaxPoolSize(int maximumPoolSize)to modify dynamically at runtime maxPoolSize. For example:

ThreadPoolExecutor executor = new ThreadPoolExecutor(...);
executor.setMaxPoolSize(newMaxPoolSize);

But there are a few points to note:

  1. IncreasemaxPoolSize : If you increase at runtime maxPoolSize, and the current number of threads is lower than the new setting maxPoolSize, the thread pool may create new threads due to tasks waiting in the queue, until the number of threads reaches the new maxPoolSizeor the queue is empty.

  2. ReducemaxPoolSize : If you reduce maxPoolSize, and the current number of threads exceeds the newly set maxPoolSize, the excess threads will not be terminated immediately. keepAliveTimeThey are only terminated when they are idle and exceeded . This means that, in some cases, the actual number of threads may be temporarily exceeded maxPoolSize.

  3. Concurrency issues : If you are dynamically modifying in a multi-threaded environment maxPoolSize, you need to ensure that such modifications do not cause race conditions or other thread safety issues.

In general, while it is possible to modify dynamically maxPoolSize, one needs to be careful when making such modifications and make sure to understand their behavior and possible consequences.

2.3 According to the following quotes, why do non-core threads start to be created when the waiting queue is full?

As can be seen from the figure, when a new task is submitted to the thread pool, the processing flow of the thread pool is as follows.
1 By default, threads are not created immediately after the thread pool is created,
but threads are created for processing when a task is submitted. (Unless the prestartCoreThread or prestartAllCoreThreads method is called)
2 When the number of threads is less than the number of core threads, a thread is created for execution every time a task is submitted, even if there are currently idle threads, until the current number of threads reaches the number of core threads.
3 When the number of current threads reaches the number of core threads, if tasks are still submitted at this time, these tasks will be placed in the work queue, and after the thread finishes processing the tasks at hand, it will come to the work queue to pick up tasks for processing.
4 The current number of threads reaches the number of core threads and the work queue is full. If tasks are still submitted at this time, threads will continue to be created for processing until the number of threads reaches the maximum number of threads.
5 The current number of threads reaches the maximum number of threads and the queue is full. If tasks are still submitted at this time, the saturation strategy will be triggered.
6 If the line control time of a thread exceeds keepAliveTime, it will be marked as recyclable, and when the current size of the current thread pool exceeds the number of core threads, the thread will be terminated.

A: The idea behind this design is the balance between efficiency and resources.

  1. Conserve resources : Core threads are usually memory resident, which means they are not recycled due to idleness. To avoid wasting resources, the designers of thread pools decided to first use core threads to process tasks until they are all busy. This strategy ensures that the thread pool always uses the minimum number of threads to process requests, thus maximizing the use of each thread.

  2. Work queue as a buffer : when core threads are busy, new tasks are put into the work queue. This queue acts as a buffer, ensuring that during short-term spikes in requests, a large number of new threads are not created immediately. This helps system throughput and reduces the overhead of thread creation and destruction.

  3. Coping with continuous high load : If the work queue is full, it means that neither the existing core threads nor the capacity of the queue can handle the current request load. At this time, the thread pool starts to create non-core threads to handle new requests . This ensures that even under high load, requests are still served rather than rejected.

  4. Protection system : A maximum number of threads is set in the design, the purpose is to ensure that in extreme cases, the thread pool will not create too many threads, thereby exhausting system resources.

In summary, this strategy is to find a balance between efficient use of resources and high load. Both thread creation and destruction have overhead, so when buffering mechanisms such as core threads and work queues are in place, it is ensured that the thread pool will run efficiently most of the time. Additional threads are only created when the load is really high enough that more threads are needed to handle it.

2.4 If the maximum number of threads reaches the threshold, but the waiting queue does not exist, should the new task be placed in the queue or create a new thread?

If the current number of threads in the thread pool has reached maxPoolSize(that is, the maximum number of threads), and a new task is submitted to the thread pool, the processing strategy at this time is as follows:

  1. If the work queue is not yet full, new tasks will be queued for execution.
  2. If the work queue is full, the thread pool RejectedExecutionHandlerprocesses this newly submitted task according to its rejection policy ( ). Common rejection strategies include throwing an exception, dropping tasks, dropping an old task in the queue to make room for a new one, etc.

So the answer is: if the maximum number of threads has reached the threshold, new incoming tasks first try to put in the waiting queue. Only when the queue is also full will the rejection policy be triggered.

2.5 What if the maximum number of threads and the waiting queue do not reach the threshold

At this time, it means that the load is gradually decreasing.

In general, when both the maximum number of threads and the waiting queue do not reach the threshold, newly submitted tasks are first attempted to be assigned to a core thread. If the number of core threads is full, new tasks will be placed in the waiting queue.

3 Discussion and optimization of rejection strategy of thread pool

3.1 What rejection strategies does the thread pool have?

When the bounded queue is full, the saturation strategy comes into play. The saturation policy of ThreadPoolExecutor can be modified by calling setRejectedExecutionHandler. (The saturation strategy is also used if a task is submitted to an Executor that has been shut down). There are the following four saturation policies, and the default AbortPolicy is generally used.

  • AbortPolicy: Abort policy. The default saturation strategy, which throws an unchecked RejectedExecutionException. The caller can catch this exception and write his own processing code according to his needs.
  • DiscardPolicy: Discard policy. When a newly submitted task cannot be queued for execution, the strategy quietly discards the task.
  • DiscardOldestPolicy: Discard the oldest policy. When the newly submitted task cannot be saved in the queue for execution, the next task to be executed will be discarded, and then a new task will be resubmitted. (If the work queue is a priority queue, the "discard oldest" policy will cause the highest priority task to be discarded, so it is best not to use the "discard oldest" policy with a priority queue).
  • CallerRunsPolicy: Caller Runs Policy. This strategy implements an adjustment mechanism that neither abandons tasks nor throws exceptions, but rolls back certain tasks to the caller (the main thread that invokes the thread pool to execute tasks), thereby reducing the number of new tasks process. It will not execute the newly submitted task in a thread in the thread pool, but execute the task in a thread that called execute. When all threads in the thread pool are occupied and the work queue is filled, the next task will be executed in the main thread (the main thread that calls the thread pool to execute the task) when execute is called. Since it takes a certain amount of time to execute the task, the main thread cannot submit the task for at least a period of time, so that the worker thread has time to process the task being executed. During this period, the main thread will not call accept, so the incoming request will be kept in the TCP layer queue. If the overload continues, the TCP layer will eventually find that its request queue is full, so it will also start to discard the request, and after the request of the tcp layer is discarded, the client will retransmit the lost packet due to the reliability mechanism, to a certain extent Consistency is guaranteed. When the server is overloaded, this overload situation will gradually spread outward - from the thread pool to the work queue to the application to the TCP layer, and finally to the client, causing the server to achieve a gentle performance degradation under high load .

3.2 Executing CallerRunsPolicy, the task can always be executed eventually, right?

Yes, CallerRunsPolicyit's a rejection tactic, but it doesn't actually deny the task. Instead, it lets the caller run the task itself when both the thread pool and the work queue are full. Typically, the caller is the thread that called execute()the method (usually the main thread). Therefore, when the thread pool cannot handle the task, the task will always be executed in the caller's thread.

This strategy ensures that tasks are always executed eventually and no tasks are discarded. This helps relieve the pressure on the thread pool, but please note that since the caller thread will be used to perform tasks, this will affect other work on the caller thread. When the number of tasks continues to increase, the caller thread may be blocked, resulting in a decrease in the responsiveness of the system. Therefore, when choosing to use CallerRunsPolicy, you need to make a trade-off according to the specific needs and scenarios of the system.

3.3 Since the thread adopts the DiscardPolicy strategy, how to deal with the abandoned thread?

DiscardPolicyis ThreadPoolExecutora rejection strategy for . When the thread pool and work queue are full, it will directly discard the newly submitted tasks. Note that it does not throw any exceptions or provide any notifications.

To deal DiscardPolicywith abandoned tasks in , the following strategies can be considered:

  1. Logging: Although DiscardPolicydoes not provide any notifications by itself, we can log discarded tasks by customizing the rejection policy. In the custom rejection policy, information such as discarded task information, time, and thread pool status can be recorded to facilitate subsequent problem analysis and troubleshooting.

  2. Adjust thread pool parameters: If you find that the thread pool often discards tasks, you can consider adjusting the parameters of the thread pool, such as increasing the size of the thread pool, increasing the capacity of the work queue, etc., so as to reduce the number of tasks being discarded.

  3. Choose another rejection strategy: If DiscardPolicyit is not suitable for the needs of the system, you can consider choosing other rejection strategies, such as AbortPolicy(throw exception), CallerRunsPolicy(caller execute task), or DiscardOldestPolicy(discard the oldest task in the queue).

  4. Design a fallback mechanism: A fallback mechanism can be designed at the application layer. For example, when a task is discarded, the task can be sent to the message queue or persisted to the database, and then re-executed when the thread pool has free resources.

  5. Current limiting or downgrading: If the system load is too high and the thread pool often discards tasks, you can consider adopting a current limiting or downgrading strategy to reduce the system load and ensure the normal operation of core functions.

In short, the discarded tasks need to be handled reasonably according to the requirements and scenarios of the system, so as to avoid affecting the stability of the system and user experience.

4 thread basics

4.1 java thread creation method

In Java, there are many ways to create threads, mainly including the following ways:

  1. Inherit the Thread class :

    • Create a new class and inherit from java.lang.Threadthe class.
    • Override run()the method to define the logic for thread execution.
    • Create an instance of this class and call start()the method to start the thread.
    class MyThread extends Thread {
          
          
        @Override
        public void run() {
          
          
            System.out.println("Thread is running");
        }
    }
    
    public class Main {
          
          
        public static void main(String[] args) {
          
          
            MyThread myThread = new MyThread();
            myThread.start();
        }
    }
    
  2. Implement the Runnable interface :

    • Create a new class and implement java.lang.Runnablethe interface.
    • Override run()the method to define the logic for thread execution.
    • Create Threadan instance of the class, Runnablepass in the implementation class as a parameter of the constructor, and call start()the method to start the thread.
    class MyRunnable implements Runnable {
          
          
        @Override
        public void run() {
          
          
            System.out.println("Runnable is running");
        }
    }
    
    public class Main {
          
          
        public static void main(String[] args) {
          
          
            Thread thread = new Thread(new MyRunnable());
            thread.start();
        }
    }
    
  3. Implement the Callable interface :

    • Create a new class and implement java.util.concurrent.Callablethe interface.
    • Override call()the method, define the logic of thread execution, and return the result.
    • Wrap the object FutureTaskwith the class Callable, pass it to Threadthe constructor of the class, and call start()the method to start the thread.
    class MyCallable implements Callable<Integer> {
          
          
        @Override
        public Integer call() {
          
          
            return 42;
        }
    }
    
    public class Main {
          
          
        public static void main(String[] args) throws ExecutionException, InterruptedException {
          
          
            FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
            Thread thread = new Thread(futureTask);
            thread.start();
            System.out.println("Callable result: " + futureTask.get());
        }
    }
    
  4. Use thread pool :

    • Create a thread pool using java.util.concurrent.Executorsthe factory method of the class.
    • Submit the task that implements Runnablethe or Callableinterface to the thread pool for execution.
    public class Main {
          
          
        public static void main(String[] args) {
          
          
            ExecutorService executorService = Executors.newFixedThreadPool(2);
            executorService.submit(() -> System.out.println("Runnable in thread pool is running"));
            executorService.shutdown();
        }
    }
    

Among the above four methods, using the thread pool is usually the first choice, because it can effectively manage and reuse thread resources, improve performance, and reduce the overhead of thread creation and destruction.

4.2 FutureTaskand CompletableFutureare both classes used to represent asynchronous calculation results in Java concurrent programming, but they have some differences in use and function.

  1. The difference in usage :

    • FutureTaskis an asynchronous computation task that can be canceled. It is often used to wrap Callableobjects, which are then Threadexecuted via .
    • CompletableFutureIt is a new class introduced by Java 8. It can not only wrap asynchronous computing tasks, but also provide more functions, such as combining multiple asynchronous tasks and handling exceptions.
  2. Functional difference :

    • FutureTaskOnly basic asynchronous task management functionality (start, cancel, get result, etc.) is provided.
    • CompletableFutureProvides rich functionality to compose, process and manipulate asynchronous tasks. For example, you can combine multiple asynchronous tasks using the thenApply, thenCompose, , etc. methods. thenCombineYou can also use methods such as handle, , exceptionallyetc. to handle exceptions.
  3. Blocking and non-blocking :

    • When using the method FutureTaskof get()to get the result, if the asynchronous task has not been completed, this method will block the current thread until the asynchronous task is completed.
    • CompletableFutureProvides a non-blocking way to process the result of an asynchronous task, such as using thenAcceptthe method to process the result.

In short, CompletableFutureit is a more powerful and flexible tool, which provides more functions to process and combine asynchronous tasks. Instead, it FutureTaskonly provides basic asynchronous task management functions. If you need to perform more complex asynchronous task operations and combinations, CompletableFutureit will be a better choice.

4.3 Analysis of poll() and take() actions in the thread pool

It is recommended to take a look at: How should the Java thread pool be used?

4.3.1 For the BlockingQueue queue of the thread pool, or the waiting queue of the object, when using the take() method, there is no message content in the blocking queue, and the core thread will be blocked. Then when there is content in the queue, what happens to the thread? Know and take it again?

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class PrintAB {
    
    
    public static void main(String[] args) {
    
    
        BlockingQueue<Character> queueA = new ArrayBlockingQueue<>(1);
        BlockingQueue<Character> queueB = new ArrayBlockingQueue<>(1);

        Thread threadA = new Thread(() -> {
    
    
            try {
    
    
                for (int i = 0; i < 10; i++) {
    
    
                    System.out.print("A ");
                    queueB.put('B');
                    queueA.take();
                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });

        Thread threadB = new Thread(() -> {
    
    
            try {
    
    
                for (int i = 0; i < 10; i++) {
    
    
                    queueB.take();
                    System.out.print("B ");
                    queueA.put('A');
                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });

        threadA.start();
        threadB.start();
    }
}

When there is nothing in the blocking queue, take()the thread calling the method will be blocked. This is achieved with Object.wait()and in Java .Object.notify()

In the internal implementation, when the thread calls take()the method and the queue is empty, the thread calls wait()the method to block itself. Then, when other threads call put()methods to add elements to the queue, it calls notify()methods to wake up the wait()thus blocked thread.

At this time, the blocked thread will be woken up, and then try to get the content in the queue again. In this way, the thread can get the newly added element.

To sum up, the blocking of the thread when the queue is empty and the wake-up when new elements are added are realized through the wait()and mechanism of the Java object. notify()Such a mechanism ensures that threads can get data in time when there is new content in the queue.

4.3.2 The wait() and notify() methods here should come from BlockingQueue

Yes, and methods BlockingQueueare used internally to block and wake up threads.wait()notify()

4.3.3 If multiple threads execute the wait() method, they are put into the waiting queue one by one. When another thread executes the notifyAll() method, all threads will be awakened to compete. If notify() is called, only the waiting queue will be awakened. The thread in the head? What if I want to wake up the thread waiting for the specified position in the queue?

When multiple threads execute wait()a method, they are placed in a waiting queue. When another thread executes notifyAll()the method, all waiting threads are woken up. These threads will compete to enter the synchronized block, but only one thread can successfully acquire the lock and continue execution.

When a method is called notify(), only one thread in the waiting queue will be woken up. You cannot specify which thread to wake up, because wait()the notify()mechanism is based on the built-in locks and monitors of Java objects, and they do not provide the ability to choose to wake up a specific thread.

If you need more flexible thread control and communication, you can use java.util.concurrentother tools in the package, such as ReentrantLockand Condition. Multiple condition variables can be created using Conditioncondition variables, allowing more precise control over which threads are woken up.

4.3.4 If calling notify() will only wake up the thread waiting for the head of the queue, or not necessarily the head

Calling notify()the method will wake up any thread in the waiting queue. Normally, it will wake up the thread that enters the blocked state first in the waiting queue (that is, the thread at the head of the queue), but this depends on the specific implementation and scheduling strategy of the JVM. Therefore, in practice, we cannot assume that it always wakes up the head thread.

Remember, the purpose of the notify()sum wait()mechanism is to simplify the cooperation and communication between multiple threads, not to provide precise thread control. If more precise control is required, consider using advanced concurrency tools such as ReentrantLockand .Condition

4.3.5 The difference between the poll() method in the thread pool and the poll() method with timestamp

The main difference between methods in the thread pool poll()and methods with timestamps is the wait time and behavior.poll(long timeout, TimeUnit unit)

  1. poll()method:

    • This method returns immediately, and if an element is available in the queue, it returns that element immediately. If the queue is empty, it will immediately return null, and then be destroyed. This method does not wait for the element to become available.
  2. poll(long timeout, TimeUnit unit)method:

    • This method waits the specified amount of time for an element to be retrieved and removed from the queue. If an element becomes available in the queue within the specified wait time, it returns that element. If the wait time elapses while the queue is still empty, it returns null, at which time it is destroyed. This method allows the thread to wait for a period of time until an element can be taken from the queue.

In general, poll()the method is non-blocking, it will return immediately; and poll()the method with timestamp is likely to block, it will wait for a period of time. When using these two methods, the appropriate method should be selected according to the specific usage scenarios and needs.

4.3.6 I see that the thread pool is used in the project written on your resume. Do you know how the thread pool realizes the reuse of threads?

The logic of thread pool multiplexing is very simple, that is, after the thread is started, through the while loop, tasks are continuously pulled from the blocking queue, so as to achieve the purpose of multiplexing threads. In fact, it is asking about poll and take methods

4.3.7 We all know that the thread pool will recycle threads that exceed the idle time, so how does the thread pool count the idle time of threads?

My answer: There may be a monitoring thread in the background that keeps counting the idle time of each thread, and when the idle time of a thread exceeds the threshold, it will be recycled.
Official answer: Blocking Queue (BlockingQueue) provides a poll(time, unit) method to pull data. The function is: When the queue is empty, it will block for a specified time and then return null. The thread pool is the method of using the blocking queue. If the task cannot be pulled within the specified time, it means that the survival time of the thread has exceeded the threshold and it will be recycled. That is to say, the thread itself multiplexes the thread itself to time and expires to self-destruct

4.3.8 How does the thread pool set the logic of exception capture

If a thread in the thread pool throws an uncaught exception and it is not caught by a try-catch block, several things will happen:

  1. An exception causes the execution of the current thread to terminate, that is, the thread no longer executes tasks.

  2. If the thread is a worker thread in the thread pool, the thread pool will detect the termination of the thread and may create a new thread to replace it, thus maintaining the number of threads in the thread pool.

  3. If the uncaught exception handler ( ) is not set UncaughtExceptionHandler, then the exception's stack trace is printed to System.err.

  4. If you set an uncaught exception handler, that handler will be called. You can perform operations such as error logging and resource cleanup in this handler.

Note: If you catch and handle exceptions in Runnableor task, then the thread pool will not know about these exceptions, so the above situation will not happen. CallableAdditionally, for tasks submitted FutureTaskwith ExecutorService.submitmethods, any exception thrown from the task will be caught and re-thrown when Future.getthe method is called ExecutionException.

In summary, to avoid thread termination and possible resource leaks due to uncaught exceptions, it is recommended to handle exceptions appropriately in task code.

4.3.9 If the UncaughtExceptionHandler is not set here, the stack information of the exception will be printed to System.err. How to implement this UncaughtExceptionHandler mechanism in java

UncaughtExceptionHandlerMechanisms in Java are setup through methods Threadof classes . setUncaughtExceptionHandlerWhen a thread terminates due to an uncaught exception, the JVM will query the thread to see if it is set UncaughtExceptionHandler, and if it is set, it will call its uncaughtExceptionmethod to handle the exception.

Here is UncaughtExceptionHandlera simple example of usage:

public class Main {
    
    

    public static void main(String[] args) {
    
    
        Thread thread = new Thread(() -> {
    
    
            throw new RuntimeException("Test Exception");
        });

        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    
    
            @Override
            public void uncaughtException(Thread t, Throwable e) {
    
    
                System.out.println("Caught exception in thread " + t.getName() + ": " + e.getMessage());
            }
        });

        thread.start();
    }
}

In this example, we create a thread and throw a while that thread is running RuntimeException. Then, we set up a method that will be called UncaughtExceptionHandlerwhen the thread terminates due to an uncaught exception, uncaughtExceptionand we can handle the exception here.

When not set UncaughtExceptionHandler, an uncaught exception causes the thread to terminate and the exception's stack information is printed to System.err. This is achieved through the methods ThreadGroupof the class . uncaughtExceptionWhen a thread terminates, the JVM invokes the method ThreadGroupof the thread's object uncaughtExceptionand prints the exception's stack information to it System.err.

Note that if you don't set it for a specific thread UncaughtExceptionHandler, the thread group's default handler will be called. You can also set a default uncaught exception handler for all threads, by calling Thread.setDefaultUncaughtExceptionHandlerthe method.

4.3.10 So when an exception occurs in a thread, the abnormal thread itself can feel it. Should the callback function be used at this time?

Yes, when an uncaught exception occurs inside a thread, the JVM checks to see if that thread is set UncaughtExceptionHandler. If set, the JVM will call the handler's uncaughtExceptionmethod, passing the exception object and the thread where the exception occurred as parameters to this method. This is in the form of a callback function that allows you to customize how uncaught exceptions are handled.

If the thread is not set UncaughtExceptionHandler, the JVM will further check whether the thread belongs to ThreadGroupan exception handler. If set, the method that will be ThreadGroupcalled uncaughtException. If not set, the JVM will call Threadthe class getDefaultUncaughtExceptionHandlermethod to find out if the default exception handler is set.

If none of the above steps find a suitable exception handler, the JVM will print the exception information to System.err.

4.3.11 If the thread executes abnormally, will it cause data inconsistency?

Thread execution exceptions may cause data inconsistencies, depending on where the exception occurs and the data operations in the thread. Here are some common situations that cause data inconsistencies:

  1. Incomplete data manipulation : If a thread terminates with an exception while modifying data, the data may be left in an inconsistent state. For example, if a thread updates some data during a transaction but an exception occurs before completing the transaction, data inconsistencies may result.

  2. Resource lock not released : If an exception occurs when a thread holds a resource lock, it may cause the lock to not be released, thereby blocking other threads, which may also lead to data inconsistency.

  3. Incorrect transaction rollback : Data inconsistency can also result if a thread fails to roll back the transaction properly if an exception occurs while executing the database transaction.

To avoid data inconsistency issues, the following measures are recommended:

  1. Exception handling : Add appropriate exception handling logic to the code to ensure that it can be restored to a consistent state under abnormal conditions.

  2. Use transactions : Use transactions in database operations to ensure data consistency. A transaction can ensure that a series of operations either all succeed or all fail.

  3. Use locks : Use appropriate lock mechanisms when accessing shared resources to ensure that resources remain consistent when accessed by multiple threads.

  4. Use atomic operations : Use atomic operations to update data in a multi-threaded environment to ensure data consistency. java.util.concurrent.atomicFor example, a series of atomic operation classes are provided in the Java package, such as AtomicInteger, , AtomicLongand so on.

In summary, abnormal thread execution can lead to data inconsistencies, but with proper design and programming practices, this can be avoided.

4.4 After jdk's thread pool is full of core threads, why do subsequent tasks be placed in the queue instead of creating threads for processing until the number reaches the threshold of the maximum number of threads?

This strategy is designed to balance the cost of thread creation with the consumption of system resources. If the thread pool keeps creating new threads, system resources (such as cpu, memory space) may be exhausted, and the creation and destruction of threads will also bring additional overhead. By putting tasks into the queue, the number of threads can be effectively controlled to avoid resource waste.

In practical applications, you can configure the parameters of the thread pool according to specific requirements and system resources to achieve the best performance.

4.5 Why do we continue to create non-core threads after the queue is full? Is the creation of more threads conducive to faster execution? What about the thread competition problem at this time, and the resource consumption caused by creation and destruction?

  1. Faster execution: In some cases, creating more threads can improve the speed of task execution, especially when the task is CPU-intensive rather than IO-intensive. Creating more threads allows multiple tasks to run at the same time, thereby increasing the parallelism of tasks and completing work in the task queue faster.
  2. Task waiting time: If there are a large number of tasks waiting to be executed in the work queue, creating additional threads can reduce the waiting time of tasks in the queue and improve responsiveness. This is very important for some applications, such as web servers, that need to respond quickly to client requests.
  3. Refuse requests as little as possible: If no thread is created after the queue is full, the thread pool will immediately adopt a rejection strategy. At this time, it should not be for servers with redundant capabilities, so create non-core threads as much as possible to process Tasks in the queue, with as few rejected requests as possible.

Guess you like

Origin blog.csdn.net/yxg520s/article/details/132525338