This is the second day of my participation in Gwen Challenge

background

Recently, while doing related work in the lab, a friend saw new Thread(…) appearing in the project code. “And swore at them. Seeing this scene, I silently deleted the new Thread(…) I had written in another place. As if nothing had happened (good thing he didn’t see XD).

In order not to make this mistake again, I wrote this article to document how Java threads should be used (without being scolded), and also to open a new pit!

Welcome to contact me if there is a mistake!

Why not use new Thread

Let’s start with my principle of simplicity and elegance. Imagine if you needed to create many threads in a piece of code, and you kept calling new Threads (…). The start ()? Obviously, this code isn’t neat or elegant at all. There are many disadvantages to such code beyond the first time:

  1. Each time a new object is created, poor performance;
  2. Many objects created are independent and lack unified management. [Fixed] Creating an infinite number of new threads in your code will cause those threads to compete with each other, taking up too much system resources and causing a crash or OOM.
  3. Lack of many features such as timed execution, interrupts, etc.

It’s easy to see from these drawbacks that the solution is to get a supervisor to centrally manage these threads, store them in a collection (or similar data structure), and dynamically assign their tasks. Of course Java already gives us something very robust to use, thread pools!

Java thread pool

Java provides a factory class to construct the thread pool we need. The factory class is Executors. This class provides many methods, but we’ll focus on the four methods it provides to create a thread pool, i.e

  • newCachedThreadPool()
  • newFixedThreadPool(int nThreads)
  • newScheduledThreadPool(int corePoolSize)
  • newSingleThreadExecutor()

newCachedThreadPool()

This method, as its name suggests, creates a cache thread pool. Caching means that the pool will create new threads as needed and will preferentially use previously created threads when new tasks are available. That is, once a thread is created, it will always be in the pool, and it will be reused for subsequent tasks after the task is executed. If there are not enough threads, it will create a new thread.

Take a piece of code as an example:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); // Create a cache thread pool

for (int i = 0; i < 10; i++) {
    final int index = i;

    // Wait a period of time, such as 1s, before releasing a task
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    // Execute the task
    cachedThreadPool.execute(() -> System.out.println(Thread.currentThread().getName() + ":" + index));
}
Copy the code

In this example, I wait 1 second before each thread is called to execute the task, which is more than enough time for the threads in the thread pool to complete the last task, so you’ll see the same thread executing the task in the output.

ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); // Create a cache thread pool

for (int i = 0; i < 10; i++) {
    final int index = i;

    // Wait a period of time, such as 1s, before releasing a task
    if (i % 2= =0) {
        try {
            Thread.sleep(1000);
        } catch(InterruptedException e) { e.printStackTrace(); }}// Execute the task
    cachedThreadPool.execute(() -> System.out.println(Thread.currentThread().getName() + ":" + index));
}
Copy the code

In this example, I control whether the thread waits or not depending on the parity difference before calling it to perform a task, so two tasks need to be executed at the same time, so one more thread is created in the thread pool as needed. You can also increase this modulus to 3, 4, 5… To see if the thread pool creates new threads on demand.

Note that the thread pool here is infinite, and we don’t specify its size. (It can’t be infinite in practice, but I’ll come back to that later in this series.)

newFixedThreadPool(int nThreads)

As you can see, this method takes an argument to create a thread pool with a fixed length. This argument is the size of the pool. That is, the number of threads that can be executed at any one time is limited to nThreads, and this thread pool effectively controls the maximum number of concurrent threads to prevent excessive resource usage. The excess threads are placed in a queue in the thread pool, waiting for other threads to finish executing. This queue is also worth investigating. It is an unbounded queue, which I will explore later in this series.

Take a piece of code as an example:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); // Create a cache thread pool of 3 size

for (int i = 0; i < 10; i++) {
    final int index = i;

    // Execute the task
    fixedThreadPool.execute(() -> {
        System.out.println(Thread.currentThread().getName() + ":" + index);

        // It takes 1 second to simulate the task execution
        try {
            Thread.sleep(1000);
        } catch(InterruptedException e) { e.printStackTrace(); }}); }Copy the code

In this example, you can see that I created a thread pool with a size of 3, which means that the maximum number of concurrent threads it supports is 3.

It is important to set the proper size of the fixed-length thread pool.

newScheduledThreadPool(int corePoolSize)

Scheduled has been applied. You can probably guess from Scheduled that this thread pool is designed to address the third disadvantage mentioned above, which is the lack of Scheduled execution. This thread pool is also fixed length, and the corePoolSize argument is the size of the thread pool, that is, the number of threads to remain in the pool in the idle state.

To implement scheduling, use the schedule() method of the new thread pool.

Take a piece of code as an example:

/ / attention! ScheduledExecutorService is changed from ExecutorService to ScheduledExecutorService, otherwise there is no timing function
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3); // Create a cache thread pool

// Execute the task
scheduledThreadPool.schedule(() -> System.out.println(Thread.currentThread().getName() + ": I will execute in 3 seconds."),
        3, TimeUnit.SECONDS);
Copy the code

This example will output in 3 seconds. Of course, you can set different timing according to different needs, and even achieve regular execution function, you can check [official API] for details.

newSingleThreadExecutor()

This thread pool is simpler, it is a single thread pool, using only one thread to perform tasks. However, unlike newFixedThreadPool(1, threadFactory), it guarantees that the created thread pool will not be reconfigured to use other threads, meaning that the threads in the pool remain the same.

Take a piece of code as an example:

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); Create a single thread pool

for (int i = 0; i < 10; i++) {
    final int index = i;

    // Execute the task
    singleThreadExecutor.execute(() -> {
        System.out.println(Thread.currentThread().getName() + ":" + index);

        // It takes 1 second to simulate the task execution
        try {
            Thread.sleep(1000);
        } catch(InterruptedException e) { e.printStackTrace(); }}); }Copy the code

As you can see in the output he’s only printing second by second, and only one thread is performing the task.

Closing the thread pool

If you run my example above, you’ll notice that the program never ends because my example code doesn’t close the thread pool. The thread pool itself provides two methods for closing:

  • shutdown(): Sets the thread pool state toSHUTDOWNAt this time,No new assignments.Wait until the task in the thread pool completes;
  • shutdownNow(): Sets the thread pool state toSHUTDOWNTo the thread poolAll threads interruptOf the calling threadinterrupt()Action), empty the queue, and returnList of tasks waiting to be executed.

It also provides methods to check whether the thread pool is closed and terminated, isShutdown() and isTerminated(), respectively.

conclusion

Instead of using new threads (…), use these four Thread pools as needed. This way!

Of course, Thread pools can only implicitly control Thread variables, so if there is a business need for custom Thread monitoring control, use new threads (…). .