5

Since most of the time this week is writing prototypes, the main problem is that the inaccurate understanding of the actual function leads to a lot of time wasted in modifying the prototype many times. This also tells us that we must clarify the actual requirements before starting.
Because threads have been mentioned many times in previous meetings, and I have no understanding of threads, I have the following article.

Why use multithreading

In the process of developing the system, we often deal with some time-consuming tasks (such as inserting a large amount of data into the database), and at this time, we need to use multithreading.

Whether the multi-threaded method is encapsulated in Springboot

Yes, multi-threaded operations can be implemented directly by @Async in Spring

How to control various parameters in thread running

By configuring the thread pool.

The execution rules of the thread pool ThreadPoolExecutor are as follows

Then let's think about constructing a thread pool to try:

 @Configuration
@EnableAsync
public class ThreadPoolConfig implements AsyncConfigurer {
  /**
   * 核心线程池大小
   */
  private static final int CORE_POOL_SIZE = 3;

  /**
   * 最大可创建的线程数
   */
  private static final int MAX_POOL_SIZE = 10;

  /**
   * 队列最大长度
   */
  private static final int QUEUE_CAPACITY = 10;

  /**
   * 线程池维护线程所允许的空闲时间
   */
  private static final int KEEP_ALIVE_SECONDS = 300;

  /**
   * 异步执行方法线程池
   *
   * @return
   */
  @Override
  @Bean
  public Executor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setMaxPoolSize(MAX_POOL_SIZE);
    executor.setCorePoolSize(CORE_POOL_SIZE);
    executor.setQueueCapacity(QUEUE_CAPACITY);
    executor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);
    executor.setThreadNamePrefix("LiMingTest");
    // 线程池对拒绝任务(无线程可用)的处理策略
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.initialize();
    return executor;
  }
}

ThreadPoolExecutor is a thread pool implementation in JDK. This class implements various methods required by a thread pool. It provides methods such as task submission, thread management, and monitoring.

corePoolSize: number of core threads

The minimum number of threads maintained by the thread pool. By default, core threads will not be reclaimed after they are created (Note: After setting allowCoreThreadTimeout=true, idle core threads will also be reclaimed if they exceed the survival time).

Threads larger than the number of core threads will be recycled after the idle time exceeds keepAliveTime.

maximumPoolSize: maximum number of threads

The maximum number of threads the thread pool is allowed to create.

When adding a task, the number of core threads is full, the thread pool has not reached the maximum number of threads, and there are no idle threads, when the work queue is full, create a new thread, and then take a task from the head of the work queue and submit it Processed by the new thread, and the task just submitted is placed at the end of the work queue.

keepAliveTime: idle thread survival time

When the idle time of a thread that can be recycled is greater than keepAliveTime, it will be recycled.
Recycled thread:

 设置allowCoreThreadTimeout=true的核心线程。
大于核心线程数的线程(非核心线程)。

workQueue: work queue

After the new task is submitted, if the number of core threads is full, it will be added to the work queue first, and then the task will be taken out of the queue when the task is scheduled. The work queue implements the BlockingQueue interface.

handler: reject policy

When the number of threads in the thread pool is full and the work queue reaches the limit, newly submitted tasks are processed using the rejection policy. The rejection strategy can be customized, and the rejection strategy needs to implement the RejectedExecutionHandler interface.

There are four JDK default rejection policies:

 AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
DiscardPolicy:丢弃任务,但是不抛出异常。可能导致无法发现系统的异常状态。
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
CallerRunsPolicy:由调用线程处理该任务。

The compiler issues a warning when we create a new thread using new Thread directly in a non-test file:

 不要显式创建线程,请使用线程池。
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题
 public class TestServiceImpl implements TestService {
  private final static Logger logger = LoggerFactory.getLogger(TestServiceImpl.class);
  @Override
  public void task(int i) {
      logger.info("任务: "+i);
  }
}
 @Autowired
  TestService testService;
  @Test
  public void test() {
    for (int i = 0; i < 50; i++) {
      testService.task(i);
    }

We can see that everything is executing as normal;
图片.png

After that I did some tests on the thread:

 class TestServiceImplTest {
  @Test
  public void test() {
    Thread add = new AddThread();
    Thread dec = new DecThread();
    add.start();
    dec.start();
    add.join();
    dec.join();
    System.out.println(Counter.count);
  }

  static class Counter {
    public static int count = 0;
  }

  class AddThread extends Thread {
    public void run() {
      for (int i=0; i<10000; i++) { Counter.count += 1; }
    }
  }

  class DecThread extends Thread {
    public void run() {
      for (int i=0; i<10000; i++) { Counter.count -= 1; }
    }
  }

A self-incrementing thread and a self-decrementing thread perform the same number of operations on 0, and the result should still be zero, but the execution result is different each time.
After searching, I found 对变量进行读取和写入时,结果要正确,必须保证是原子操作。原子操作是指不能被中断的一个或一系列操作 .
For example, for the statement: n +=1; appears to be a single line statement but includes 3 instructions:

 读取n, n+1, 存储n;

For example, the following two processes add 1 to 10 at the same time

This shows that under the multi-threading model, to ensure the correct logic, when reading and writing shared variables, a set of instructions must be executed atomically: that is, when a thread executes, other threads must wait.

 static class Counter {
    public static final Object lock = new Object();//每个线程都需获得锁才能执行
    public static int count = 0;
  }

  class AddThread extends Thread {
    public void run() {
      for (int i=0; i<10000; i++) {
        synchronized(Counter.lock) { static class Counter {
    public static final Object lock = new Object();
    public static int count = 0;
  }

  class DecThread extends Thread {
    public void run() {
      for (int i=0; i<10000; i++) {
        synchronized(Counter.lock) {
          Counter.count -= 1;
        }
      }
    }
  }

It is worth noting that each class can set multiple locks, if the thread does not acquire the same lock, the above function cannot be achieved;

There are also many types of locks defined in springBoot, which will not be explained here. What we can do now is to pay attention to the asynchronous operations in the project, observe the threads used in the operations, and encounter such types in future projects. Problems can be found and solved in time.


李明
441 声望18 粉丝