[Yugong series] January 2022 Java Teaching Course 62 - asynchronous thread pool

Posted by stallingjohn on Tue, 18 Jan 2022 13:37:21 +0100

Article catalog

1, Asynchronous thread pool

1. Thread status introduction

When a thread is created and started, it neither enters the execution State as soon as it is started, nor is it always in the execution State. Thread objects have different states in different periods. The thread State in Java is defined in Java lang.Thread. In the State enumeration class, the source code of the State enumeration class is as follows:

public class Thread {
    
    public enum State {
    
        /* newly build */
        NEW , 

        /* Operational status */
        RUNNABLE , 

        /* Blocking state */
        BLOCKED , 

        /* Infinite waiting state */
        WAITING , 

        /* Timed waiting  */
        TIMED_WAITING , 

        /* termination */
        TERMINATED;
    
	}
    
    // Gets the status of the current thread
    public State getState() {
        return jdk.internal.misc.VM.toThreadState(threadStatus);
    }
    
}

Through the source code, we can see that there are six thread states in Java. The meaning of each thread state is as follows

Thread state

Specific meaning

NEW

The state of a thread that has not been started. It is also called initial state and start state. The thread was just created but not started. The start method has not been called. MyThread t = new MyThread() has only thread images and no thread characteristics.

RUNNABLE

When we call the start method of the thread object, the thread object enters the RUNNABLE state. At this time, a thread is really created in the JVM process. Once the thread is started, it is not executed immediately. Whether the thread runs or not depends on the command and CPU scheduling. Then we call this intermediate state RUNNABLE, that is, it is qualified for execution, but it is not really executed, but waiting for the degree of CPU.

BLOCKED

When a thread attempts to obtain an object lock and the object lock is held by other threads, the thread enters the Blocked state; When the thread holds a lock, the thread becomes Runnable.

WAITING

The state of a waiting thread. It is also called waiting state. There are two reasons for thread waiting: calling object Wait() and join() methods. A thread in a waiting state is waiting for another thread to perform a specific operation. For example, a thread waiting for wait() is waiting for another thread to call notify() or notifyAll(); A thread waiting for join () is waiting for another thread to end.

TIMED_WAITING

The state of a thread waiting for a limited time. It is also called time limited waiting state. There are three reasons for the thread time limited wait state: thread sleep(long),Object.wait(long),join(long).

TERMINATED

The state of a fully running thread. It is also called termination state and end state

The transition of each state is shown in the following figure:

2. Thread status - Exercise 1

Purpose: this case mainly demonstrates time_ State transition of waiting.

**Requirements: * * write a piece of code to display the status of a thread in turn: New - > runnable - > time_ WAITING -> RUNNABLE -> TERMINATED

In order to simplify our development, we use anonymous inner classes combined with lambda expressions to use multithreading.

code implementation

public class ThreadStateDemo01 {

    public static void main(String[] args) throws InterruptedException {

        //Define an internal thread
        Thread thread = new Thread(() -> {
            System.out.println("2.implement thread.start()After that, the status of the thread:" + Thread.currentThread().getState());
            try {
                //Sleep for 100ms
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("4.implement Thread.sleep(long)After completion, the status of the thread:" + Thread.currentThread().getState());
        });

        //Gets the status before start()
        System.out.println("1.adopt new Initialize a thread, but not yet start()Status of the thread before:" + thread.getState());

        //Start thread
        thread.start();

        //Sleep for 50 ms
        Thread.sleep(50);

        //Because thread1 needs to sleep for 100 milliseconds, the thread is in the sleep state in the 50th Ms
        System.out.println("3.implement Thread.sleep(long)Status of the thread when:" + thread.getState());

        //thread1 and main threads actively sleep for 150 milliseconds, so the thread has already been executed in the 150th milliseconds
        Thread.sleep(100);

        System.out.println("5.After the thread is executed, the status of the thread:" + thread.getState() + "\n");

    }

}

console output

1.adopt new Initialize a thread, but not yet start()Status of the thread before: NEW
2.implement thread.start()After that, the status of the thread: RUNNABLE
3.implement Thread.sleep(long)Status of the thread when: TIMED_WAITING
4.implement Thread.sleep(long)After completion, the status of the thread: RUNNABLE
5.After the thread is executed, the status of the thread: TERMINATED

3. Thread status - Exercise 2

Purpose: this case mainly demonstrates the state transition of WAITING.

**Requirements: * * write a piece of code to display the status of a thread in turn: New - > runnable - > waiting - > runnable - > terminated

Code implementation:

public class ThreadStateDemo02 {

    public static void main(String[] args) throws InterruptedException {

        //Define an object to lock and unlock
        Object obj = new Object();

        //Define an internal thread
        Thread thread1 = new Thread(() -> {
            System.out.println("2.implement thread.start()After that, the status of the thread:" + Thread.currentThread().getState());
            synchronized (obj) {
                try {

                    //thread1 needs to sleep for 100 milliseconds
                    Thread.sleep(100);

                    //After thread 1100 milliseconds, the obj object is released through the wait() method
                    obj.wait();
                    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("4.cover object.notify()After the method wakes up, the status of the thread:" + Thread.currentThread().getState());
        });

        //Gets the status before start()
        System.out.println("1.adopt new Initialize a thread, but not yet start()Status of the thread before:" + thread1.getState());

        //Start thread
        thread1.start();

        //main thread sleeps for 150 milliseconds
        Thread.sleep(150);

        //Because thread1 enters the wait state in the 100th millisecond, its state can certainly be obtained in the 150th second
        System.out.println("3.implement object.wait()Status of the thread when:" + thread1.getState());

        //Declare another thread to unlock
        new Thread(() -> {
            synchronized (obj) {
                //Wake up waiting threads
                obj.notify();
            }
        }).start();

        //The main thread sleeps for 10 milliseconds and waits for thread1 thread to wake up
        Thread.sleep(10);

        //Gets the state after the end of thread1 run
        System.out.println("5.After the thread is executed, the status of the thread:" + thread1.getState() + "\n");

    }

}

Console output results

1.adopt new Initialize a thread, but not yet start()Status of the thread before: NEW
2.implement thread.start()After that, the status of the thread: RUNNABLE
3.implement object.wait()Status of the thread when: WAITING
4.cover object.notify()After the method wakes up, the status of the thread: RUNNABLE
5.After the thread is executed, the status of the thread: TERMINATED

4. Thread status - exercise 3

Purpose: this case mainly demonstrates the state transition of BLOCKED.

**Requirements: * * write a piece of code to display the status of a thread in turn: New - > runnable - > blocked - > runnable - > terminated

public class ThreadStateDemo03 {

    public static void main(String[] args) throws InterruptedException {

        //Define an object to lock and unlock
        Object obj2 = new Object();

        //Define a thread that preempts the lock of obj2 object first
        new Thread(() -> {
            synchronized (obj2) {
                try {
                    Thread.sleep(100);              //The first thread holds the lock for 100 milliseconds
                    obj2.wait();                          //Then, the wait() method is used to wait and release the object lock of obj2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        //Define the target thread to obtain the lock waiting for obj2
        Thread thread = new Thread(() -> {
            System.out.println("2.implement thread.start()After that, the status of the thread:" + Thread.currentThread().getState());
            synchronized (obj2) {
                try {
                    Thread.sleep(100);              //thread3 holds the object lock for 100 milliseconds
                    obj2.notify();                        //Then wake up all the threads waiting on ojb2 through the notify() method to continue to perform subsequent operations
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("4.Status of thread after blocking:" + Thread.currentThread().getState());
        });

        //Gets the status before start()
        System.out.println("1.adopt new Initialize a thread, but not yet thread.start()Status of the thread before:" + thread.getState());

        //Start thread
        thread.start();

        //Wait 100 milliseconds first
        Thread.sleep(50);

        //It takes at least 100 milliseconds for the first thread to release the lock, so at the 50th MS, the thread is blocking waiting for obj's object lock
        System.out.println("3.When blocked due to waiting for a lock, the status of the thread:" + thread.getState());

        //Wait another 300 milliseconds
        Thread.sleep(300);

        //The execution time of the two threads plus the 50 milliseconds waiting before is 250 milliseconds in total, so all threads have been executed in the 300th Ms
        System.out.println("5.After the thread is executed, the status of the thread:" + thread.getState());

    }

}

Console output results

1.adopt new Initialize a thread, but not yet thread.start()Status of the thread before: NEW
2.implement thread.start()After that, the status of the thread: RUNNABLE
3.When blocked due to waiting for a lock, the status of the thread: BLOCKED
4.Status of thread after blocking: RUNNABLE
5.After the thread is executed, the status of the thread: TERMINATED

5. Thread pool - Fundamentals

summary:

When it comes to pools, you should think of pools. A pool is a container in which a lot of water is stored. So what is a thread pool? Thread pool can also be seen as a pool in which many threads are stored.

Significance of thread pool:

The cost of creating a thread in the system is relatively high, because it involves interaction with the operating system. When a large number of threads with very short lifetime need to be created in the program, the resource consumption of the system caused by frequent thread creation and destruction may be greater than that of business processing

To control the consumption of resources, it is a bit of "discarding the basics and discarding the end". In this case, in order to improve performance, we can use thread pool. When the thread pool is started, a large number of idle threads will be created. When we submit tasks to the thread pool, the thread pool will

A thread is started to perform the task. After the task is executed, the thread does not die, but returns to the thread pool again, which is called idle state. Wait for the execution of the next task.

Design idea of thread pool:

  1. Prepare a task container
  2. Start multiple (2) consumer threads at once
  3. At first, the task container was empty, so all threads were wait ing
  4. Until an external thread throws a "task" into the task container, a consumer thread will be awakened
  5. The consumer thread takes out the "task" and executes the task. After execution, it continues to wait for the next task

6. Thread pool - Executors default thread pool

Overview: JDK also implements the thread pool. In real enterprise development, we rarely customize the thread pool, but use the thread pool built in JDK.

We can use the static methods provided in Executors to create thread pools

static ExecutorService newCachedThreadPool() creates a default thread pool static newFixedThreadPool(int nThreads) creates a thread pool with a specified maximum number of threads

Code implementation:

package com.itheima.mythreadpool;


//static ExecutorService newCachedThreadPool() creates a default thread pool
//static newFixedThreadPool(int nThreads) 	     Create a thread pool that specifies the maximum number of threads

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {

        //1. Create a default thread pool object The pool is empty by default By default, it can accommodate up to the maximum value of int type
        ExecutorService executorService = Executors.newCachedThreadPool();
        //Executors --- can help us create thread pool objects
        //ExecutorService --- can help us control the thread pool

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "In execution");
        });

        //Thread.sleep(2000);

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "In execution");
        });

        executorService.shutdown();
    }
}

7. Thread pool - Executors creates a thread pool with a specified upper limit

Use the static methods provided in Executors to create a thread pool

static ExecutorService newFixedThreadPool(int nThreads): creates a thread pool with a specified maximum number of threads

Code implementation:

package com.itheima.mythreadpool;

//static ExecutorService newFixedThreadPool(int nThreads)
//Create a thread pool that specifies the maximum number of threads

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class MyThreadPoolDemo2 {
    public static void main(String[] args) {
        //The parameter is not the initial value, but the maximum value
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;
        System.out.println(pool.getPoolSize());//0

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "In execution");
        });

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "In execution");
        });

        System.out.println(pool.getPoolSize());//2
//        executorService.shutdown();
    }
}

8. Thread pool - ThreadPoolExecutor

To create a thread pool object:

ThreadPoolExecutor ThreadPoolExecutor = new ThreadPoolExecutor (number of core threads, maximum number of threads, maximum lifetime of idle threads, task queue, creation of thread factory, rejection policy of task);

Code implementation:

package com.itheima.mythreadpool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThreadPoolDemo3 {
//    Parameter 1: number of core threads
//    Parameter 2: maximum number of threads
//    Parameter 3: maximum idle thread lifetime
//    Parameter 4: time unit
//    Parameter 5: task queue
//    Parameter 6: create thread factory
//    Parameter 7: task rejection policy
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        pool.shutdown();
    }
}

9. Thread pool - parameter details

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
    
corePoolSize:    The maximum value of core thread cannot be less than 0
maximumPoolSize: The maximum number of threads cannot be less than or equal to 0, maximumPoolSize >= corePoolSize
keepAliveTime:   Maximum idle thread lifetime,Cannot be less than 0
unit:            Time unit
workQueue:       Task queue, cannot be empty null
threadFactory:   Create thread factory,Cannot be null      
handler:         Task rejection policy,Cannot be null  

10. Thread pool - non default task rejection policy

RejectedExecutionHandler is a task rejection policy interface provided by jdk. There are four subclasses below it.

ThreadPoolExecutor.AbortPolicy: 		    Discard task and throw RejectedExecutionException Abnormal. Is the default policy.
ThreadPoolExecutor.DiscardPolicy:  		   Discarding the task without throwing an exception is not recommended.
ThreadPoolExecutor.DiscardOldestPolicy:     Discard the longest waiting task in the queue, and then add the current task to the queue.
ThreadPoolExecutor.CallerRunsPolicy:        To invoke a task run()Method bypasses the thread pool and executes directly.

Note: specify the number of tasks that can be executed by the thread pool = queue capacity + maximum number of threads

Case demonstration 1: demonstrate ThreadPoolExecutor Abortpolicy task processing policy

public class ThreadPoolExecutorDemo01 {

    public static void main(String[] args) {

        /**
         * The number of core threads is 1, the maximum number of thread pools is 3, the capacity of task container is 1, and the maximum lifetime of idle threads is 20s
         */
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;

        // Five tasks are submitted, and the thread pool can handle up to four tasks. When we use the AbortPolicy task processing policy, an exception will be thrown
        for(int x = 0 ; x < 5 ; x++) {
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "---->> Performed the task");
            });
        }
    }
}

Console output results

pool-1-thread-1---->> Performed the task
pool-1-thread-3---->> Performed the task
pool-1-thread-2---->> Performed the task
pool-1-thread-3---->> Performed the task

The console reports an error. Only four tasks have been executed, and one task has been discarded

Case demonstration 2: demonstrate ThreadPoolExecutor Discardpolicy task processing policy

public class ThreadPoolExecutorDemo02 {
    public static void main(String[] args) {
        /**
         * The number of core threads is 1, the maximum number of thread pools is 3, the capacity of task container is 1, and the maximum lifetime of idle threads is 20s
         */
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardPolicy()) ;

        // Five tasks are submitted, and the thread pool can process up to four tasks. When we use the task processing policy of DiscardPolicy, the console will not report an error
        for(int x = 0 ; x < 5 ; x++) {
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "---->> Performed the task");
            });
        }
    }
}

Console output results

pool-1-thread-1---->> Performed the task
pool-1-thread-1---->> Performed the task
pool-1-thread-3---->> Performed the task
pool-1-thread-2---->> Performed the task

The console did not report an error. Only four tasks were executed, and one task was discarded

Demo case: poolthread.3 Discardoldestpolicy task processing policy

public class ThreadPoolExecutorDemo02 {
    public static void main(String[] args) {
        /**
         * The number of core threads is 1, the maximum number of thread pools is 3, the capacity of task container is 1, and the maximum lifetime of idle threads is 20s
         */
        ThreadPoolExecutor threadPoolExecutor;
        threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardOldestPolicy());
        // Submit 5 tasks
        for(int x = 0 ; x < 5 ; x++) {
            // Define a variable to specify the currently executed task; This variable needs to be modified by final
            final int y = x ;
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "---->> Performed the task" + y);
            });     
        }
    }
}

Console output results

pool-1-thread-2---->> Task 2 was performed
pool-1-thread-1---->> Task 0 executed
pool-1-thread-3---->> Task 3 was performed
pool-1-thread-1---->> Task 4 was performed

Task 1 was discarded because it waited the longest in the process pool.

Case demonstration 4: demonstrate ThreadPoolExecutor Callerrunspolicy task processing policy

public class ThreadPoolExecutorDemo04 {
    public static void main(String[] args) {

        /**
         * The number of core threads is 1, the maximum number of thread pools is 3, the capacity of task container is 1, and the maximum lifetime of idle threads is 20s
         */
        ThreadPoolExecutor threadPoolExecutor;
        threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.CallerRunsPolicy());

        // Submit 5 tasks
        for(int x = 0 ; x < 5 ; x++) {
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "---->> Performed the task");
            });
        }
    }
}

Console output results

pool-1-thread-1---->> Performed the task
pool-1-thread-3---->> Performed the task
pool-1-thread-2---->> Performed the task
pool-1-thread-1---->> Performed the task
main---->> Performed the task

From the console output, we can see that the secondary policy does not execute the task through the thread in the thread pool, but directly calls the run() method of the task to bypass the thread pool.