Self use Java learning (multithreading advanced)

Keywords: Java Back-end

Thread pool [understand]

Status of the thread

NEW
     Threads that have not yet started are in this state.  
RUNNABLE
     The thread executing in the Java virtual machine is in this state.  
BLOCKED
     A thread that is blocked and waiting for a monitor lock is in this state.  
WAITING
     A thread that waits indefinitely for another thread to perform a particular operation is in this state.  
TIMED_WAITING
     A thread waiting for another thread to perform an operation depending on the specified waiting time is in this state.  
TERMINATED
     The exited thread is in this state.  

 

We frequently create and destroy threads, which consumes system resources and wastes time. Therefore, the API of Java language provides us with thread pool technology to help us solve this problem.

When you create a thread pool, you actually create a container that can store threads. If you need to perform thread tasks, take a thread out of the thread pool and return it to the thread pool after use.

Create a default thread pool [Master]

The API provides a tool class called Executors, which can be used to generate thread pools with different characteristics.

public static  ExecutorService newCachedThreadPool() 
     Create a thread pool where you can create new threads as needed. The maximum number of threads that can create int max
public static ExecutorService newFixedThreadPool(int nThreads)  
     Create a fixed length thread pool
public static ExecutorService newSingleThreadExecutor() 
    Create a thread pool for a single thread
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
      Create a thread pool that can schedule commands to run after a given delay or execute periodically. 

  • Take the newCachedThreadPool method as an example to demonstrate submitting tasks to the thread pool

//Create thread pool
//1. You can create a new thread pool for threads as needed
ExecutorService executorService = Executors.newCachedThreadPool();

//2. Create a fixed length thread pool
//ExecutorService service = Executors.newFixedThreadPool(5);
//3. Create a thread pool for a single thread
//ExecutorService serive = Executors.newSingleThreadExecutor();

//Submit thread task
executorService.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"Yes");
    }
});

//Submit thread task
executorService.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"Yes");
    }
});
//Close thread pool
executorService.shutdown(); 

Execute a thread task periodically

ScheduledExecutorService 
     executorService = Executors.newScheduledThreadPool(3);
        //Perform a task periodically
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"Yes");
            }
        },3,5, TimeUnit.SECONDS);

ThreadPoolExecutor creates a thread pool

Thread pool can be understood as a hot pot shop. Six tables can be opened in the shop. If there are many guests, three more tables can be opened temporarily. When there are few guests, put away the three temporary tables. When the guests continue to increase and the frequently opened tables and temporary tables are used up, the queuing mechanism will be enabled, and other tasks will be queued in the blocking queue

Six normally open tables     --        Number of core threads
Temporary 3 tables     --          Number of temporary threads
Maximum number of tables in the store--        Maximum number of threads
Queuing channel       --      Blocking queue

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
Parameter interpretation:
    corePoolSize - number of core threads.
    maximumPoolSize - maximum number of threads.
    keepAliveTime - the time that the temporary thread will survive.
    unit - keepAliveTime time unit.
    workQueue - block the queue.
    threadFactory -   Create a factory for threads.
    handler -   If the maximum number of threads is exceeded, the rejection scheme is.  

Demonstration of creating thread pool

ThreadPoolExecutor poolExecutor=new ThreadPoolExecutor(
    6, //Number of core threads
    9, //Maximum number of threads
    3, //free time
    TimeUnit.SECONDS, //Time unit: Second
    new ArrayBlockingQueue<>(10),
    Executors.defaultThreadFactory(), //The default factory object, which helps us produce threads
    new ThreadPoolExecutor.AbortPolicy() //Way of rejection
); 

//Submit task
poolExecutor.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"Yes");
    }
}); 

Reject policy

When the submitted thread task exceeds the maximum number of threads + blocking queue length, the reject policy will be triggered.

static class ThreadPoolExecutor.AbortPolicy 
           Handler for the rejected task, which will throw RejectedExecutionException 
static class ThreadPoolExecutor.CallerRunsPolicy 
           The handler for the rejected task, which directly runs the rejected task in the calling thread of the execute method; If the executor is closed, the task is discarded. (let the main thread perform redundant tasks)
static class ThreadPoolExecutor.DiscardOldestPolicy 
           The handler for the rejected task, which discards the oldest unprocessed request and then retries execute; If the executor is closed, the task is discarded. 
static class ThreadPoolExecutor.DiscardPolicy 
           Handler for rejected tasks, which discards rejected tasks by default. 

Thread safety details [understand]

volatile keyword

When multiple threads access shared data, they do not directly access the data of the main memory, but create a variable copy in the local memory of the thread, assign a value to the copy, and then re assign it to the main memory.

There may be different values of variable copies temporarily stored by multiple threads. Modifying this variable with volatile can solve the problem and force threads to obtain the latest value from main memory every time they use variables.

Atomicity

Atomicity means that multiple operations are indivisible atomic items. They either succeed or fail at the same time. For example, when a multithread performs an auto increment operation, it is not atomic. [local memory]

For the auto increment operation, count + + is taken as an example. In fact, the bottom layer is to complete three steps (these three steps are inseparable)
    1. Copy a variable copy from main memory to thread local memory
    2. Operate on the variable copy
    3. Write the value of the variable copy back to the main memory

However, due to the randomness of the CPU, one thread may not complete these three steps, and the execution right is robbed by other threads, which destroys atomicity.

AtomicInteger class

Directly using int or Integer to add, modify and obtain variables can not guarantee atomicity, and thread safety problems may occur.

synchronized can be used to ensure thread safety. The way to solve problems in this way is from the perspective of pessimism, which is called pessimism lock.

To solve the problem of atomicity, Java provides a series of atomic classes. For example, AtomicInteger class is one of them. It also represents integers and provides some methods for self increment, acquisition and modification of variables. However, these methods can ensure atomicity and thread safety.

public int incrementAndGet()  
    The integer wrapped by AtomicInteger is self incremented first and then obtained.  // Equivalent to int num=10;   int c = ++num;

public int getAndincrement()
    For the integer wrapped by AtomicInteger, it is obtained first and then self incremented.  // Equivalent to int num=10;   int c= num++;

public int getAndAdd(int delta)  
    For the integer wrapped by AtomicInteger, get it first and then increase it.  // Equivalent to int num=10;
                                          //     int temp=num; // Get the value of num first
                                          //     num+=5; // Increase the value of num by 5
public int addAndGet(int delta)  
      For the integer wrapped by AtomicInteger, increase it first and then obtain it       // Equivalent to int num=10;  
                                           //       num+=5; / Add 5 to num
                                           //       int temp=num; // Value after assignment
public int getAndSet(int newValue)  
      For the integer wrapped by AtomicInteger, get it first and then set it

//Create atomic integer 10
AtomicInteger ai = new AtomicInteger(10);

//Get the original value first, and then increase it by 5
int result = ai.getAndAdd(5);
System.out.println("Original value:" + result);//10
System.out.println("Added value:" + ai);//15

//Get the original value first, and then set the new value
result = ai.getAndSet(20);
System.out.println("Original value:" + result); //15
System.out.println("New value:" + ai); //20

The principle of AtomicInteger class to ensure atomicity is shown in the following figure

 

Common thread safe classes [learn]

ArrayList and Vector
     ArrayList: array structure, thread unsafe (high efficiency)
     Vector: array structure, thread safe (inefficient)

HashMap and Hashtable  
     HashMap: hash table structure (array + linked list), thread unsafe (high efficiency)
     Hashtable: hash table structure (array + linked list), thread safe (synchronous code block, low efficiency)
     ConcurrentHashMap: hash table structure (array + linked list + red black tree), thread safe (synchronous code block + CAS algorithm, high efficiency)  
   
        
StringBuilder and StringBuffer
     StringBuilder: thread unsafe (high efficiency)
     StringBuffer: thread safe (inefficient)  

 

CountDownLatch class

Usage scenario: when a thread needs to be executed after other threads are executed.

//First child
public class MyChildThread1 extends Thread {
    private CountDownLatch cdl;
    //Use the construction method to assign a value to cdl
    public MyChildThread1(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        //1. Children eat
        System.out.println("Xiao Gang has just finished eating dumplings");
        //2. Say it after eating
        cdl.countDown();
    }
}


//The second child
public class MyChildThread2 extends Thread {
    private CountDownLatch cdl;

    //Use the construction method to assign a value to cdl
    public MyChildThread2(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        //1. Children eat
        System.out.println("Xiao Huang has finished his dumplings");
        //2. Say it after eating
        cdl.countDown();
    }
}


//Mother thread, wait for the first two child threads to execute before executing.
public class MyMother extends Thread{
    private CountDownLatch cdl;
    //Use the construction method to assign a value to cdl
    public MyMother(CountDownLatch cdl) {
        this.cdl = cdl;
    }

    @Override
    public void run() {
        //1. Wait first
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2. Clean up the dishes and chopsticks
        System.out.println("After dinner and washing"); 
    }
}



public static void main(String){
    //Create a CountDownLatch object to record the number of threads executed
    CountDownLatch cd = new CountDownLatch(2);
    //Three threads share a CountDownLatch object (counter)
    new MyMother(cd).start();
    new MyChildThread1(cd).start();
    new MyChildThread2(cd).start();
}

Semaphore class

SemaPhore class is used to control the number of threads being executed. It is equivalent to an administrator role. It can issue licenses to threads. Only threads with licenses can execute. Threads without licenses must wait. SemaPhore's construction method allows you to specify how many threads are licensed.

 public class MyRunnable implements Runnable{
    //Create an object that controls the number of threads executed to 2
    Semaphore sp = new Semaphore(2); 
    @Override
    public void run(){
        try {
            //Issue peer license
            sp.acquire(); 

            //Code executed by thread
            System.out.println(Thread.currentThread().getName()+"Yes");
            Thread.sleep(3000);

            //Release peer license
            sp.release();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}



public class Demo6 {
    public static void main(String[] args) {
        MyRunnable mr=new MyRunnable();

        for (int i = 0; i < 100; i++) {
            new Thread(mr,"thread "+i).start();
        }
    }
}

 

 

 

 

 

Posted by mikew2 on Mon, 29 Nov 2021 03:43:48 -0800