A brief introduction to the Executor framework and thread pool

Executor framework

 

Executor

It is an interface that only requires the implementation of void execute(Runnable command);, and the requirement is to complete a task in various situations (newly opened threads, thread pools, etc.), synchronously (execute immediately in the execute method) or asynchronously.



Define your own class to implement the Executor interface:

public class T_MyExecutor implements Executor {

    @Override

    public void execute(Runnable command) {

         command.run();

    }

    public static void main(String[] args) {

        new T_MyExecutor().execute(()->{

 

            System.out.println("hello executor");

        });

}

}

ExecutorService

The interface, inherited from Executor, extends Executor and adds some life cycle management methods.

The life cycle of an ExecutorService has three states: running, shutting down, and aborting.

When ExecutorService.shutdown() is called, it is in the shutdown state, and the isShutdown() method returns true. New tasks cannot be added at this time.

After all the added tasks are closed, the Executor is in the terminated state, and isTerminated() returns true. This requires that shutdown or shutdownNow must have been called before. You can use awaitTermination blocking to wait for all tasks to be closed.

In addition, more submit and invoke are called:

The difference between submit and executor is that its input parameter can be Runnable or Callable, and it has a return value Future<?>

The incoming parameters of the invoke system (invokeAll, invokeAny) all have Collection<? extends Callable<T>> tasks, and return T or a collection of T.

Future FutureTask

Future<V> is an interface that specifies the operation to be executed asynchronously. The result of the operation can be obtained through the get() method. If the asynchronous operation has not been completed, get() will block the current thread and you can specify the waiting time. The cancel method cancels the operation.

FutureTask<V> is a class that implements RunnableFuture<V>. This interface inherits from Runnable and Future<V>
holds a Callable<V>. The constructor needs a Callable<V> but can also accept a Runnable
(transformation this.callable = Executors.callable(runnable, result);)
The principle of asynchronous:

  • Holding a volatile int state, the current state on the surface, whether it is newly built, completed, or cancelled.
  • The internal class WaitNode is a linked list that stores threads. The current node points to the current thread, and next points to the next available thread. Change the running thread through CAS operation.
  • Through LockSupport.park blocking the acquisition before the completion of the operation, LockSupport.unpark unblocking.


CompletableFuture

Provides another way of asynchronous program execution: callbacks, do not need to block the thread to obtain asynchronous results like future.get() or use isDone to detect whether the asynchronous thread is completed to execute subsequent programs.

· Ability to manage multiple asynchronous processes, and select the completed asynchronous process to return results as needed.


Management of multiple CompletableFuture tasks

In real applications, there may be multiple asynchronous tasks at the same time. Sometimes we need them to complete together to perform the following operations, and sometimes we only need to return when there is a result.

The following cases:

 

Suppose you can provide a service

This service queries the prices of the same type of products on major e-commerce websites and displays them together

 

public class T_CompletableFuture {

 

    public static void main(String[] args) {

        long start, end;

 

        start = System.currentTimeMillis();

 

        CompletableFuture<Double> futureTM = CompletableFuture.supplyAsync(() ->

                priceOfJD ());

 

        CompletableFuture<Double> futureTB = CompletableFuture.supplyAsync(() ->

                priceOfTB ());

 

        CompletableFuture<Double> futureJD = CompletableFuture.supplyAsync(() ->

                priceOfTM ());

 

        CompletableFuture.allOf(futureTM, futureTB, futureJD).join();

 

        end = System.currentTimeMillis();

 

        System.out.println("use completable future! " + (end - start));

 

        try {

            System.in.read();

        } catch (IOException e) {

            e.printStackTrace ();

        }

 

    }

 

    private static double priceOfTM() {

        delay();

        return 1.00;

    }

 

    private static double priceOfTB() {

        delay();

        return 2.00;

    }

 

    private static double priceOfJD() {

        delay();

        return 3.00;

    }

 

 

    private static void delay() {

        int time = new Random().nextInt(500);

        try {

            TimeUnit.MILLISECONDS.sleep(time);

        } catch (InterruptedException e) {

            e.printStackTrace ();

        }

        System.out.printf("After %s sleep!\n", time);

    }

}

 

 

 

ThreadPoolExecutor

ThreadPoolExecutor inherits from AbstractExecutorService, which implements ExecutorService.
First, it implements the methods,
constructors and parameters of ExecutorService :


 

· CorePoolSize: the number of core threads

· MaximumPoolSize: the maximum number of threads

· KeepAliveTime: thread idle time

· TimeUnit time scale

· WorkQueue blocking queue, used to store waiting threads

· ThreadFactory thread factory, to provide threads for internal workers

· RejectedExecutionHandler: task rejection processor
provides four ways to deal with task rejection strategy

  1. Discard directly (DiscardPolicy)
  2. Discard the oldest task in the queue (DiscardOldestPolicy).
  3. Throw an exception (AbortPolicy)
  4. Assign tasks to the calling thread for execution (CallerRunsPolicy).

for example:

public class T_HelloThreadPool {

 

    static class Task implements Runnable {

 

        private int i;

 

        public Task(int i) {

            this.i = i;

        }

 

        @Override

        public void run() {

            System.out.println(Thread.currentThread().getName() + "Task" + i);

            try {

                System.in.read();

            } catch (IOException e) {

                e.printStackTrace ();

            }

        }

 

        @Override

        public String toString() {

            return "Task{" + "i=" + i + "}";

        }

    }

 

    public static void main(String[] args) {

        ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,

                new ArrayBlockingQueue<>(4),

                Executors.defaultThreadFactory(),

                new ThreadPoolExecutor.CallerRunsPolicy());

 

        for (int i = 0; i < 8; i++) {

            tpe.execute(new Task(i));

        }

 

        System.out.println(tpe.getQueue());

 

        tpe.execute(new Task(100));

 

        System.out.println(tpe.getQueue());

 

        tpe.shutdown();

 

    }

}



result:

 

 

The core thread is 2, the maximum thread is 4, the capacity of the task queue is 4, and the maximum can put 8 tasks.

When the ninth task is added, the ninth task is called by the main thread due to the selection of the CallerRunsPolicy rejection policy

 

There is a worker class as the task executor, HashSet<Worker> workers as the thread pool, and workQueue as the waiting queue

The state is saved by final AtomicInteger ctl.
Five states:

  • RUNNING accept new tasks and process tasks in the queue
  • SHUTDOWN does not accept new tasks but processes tasks in the queue
  • STOP does not accept new tasks, does not process tasks in the queue, and interrupts tasks in processing
  • TIDYING all tasks are closed (terminated), workerCount is 0, will call terminated 
  • TERMINATED terminated has been called



State transition:

  • RUNNING -> SHUTDOWN calls shutdown(), possibly implicitly in finalize()
  • (RUNNING or SHUTDOWN) -> STOP when shutdownNow() is called
  • SHUTDOWN -> TIDYING when the queue and pool are empty
  • STOP -> TIDYING when the pool is empty
  • TIDYING -> TERMINATED when the terminated() call ends

execute strategy:
Proceed in 3 steps:

  • When threads less than corePoolSize are running, try to create a new thread to handle this task. The addWorker method checks the running status and workerCount.
  • If a thread can be removed from the queue, check the status to determine whether to add a new thread or cancel the task
  • If you can't get a thread from the queue, add a new thread. Cancel this task if it fails.



The working process of the thread pool is as follows (transfer to individual summary):

  1. When the thread pool was first created, there was no thread in it. The task queue is passed in as a parameter. However, even if there are tasks in the queue, the thread pool will not execute them immediately.
  2. When calling the execute() method to add a task, the thread pool will make the following judgments:
    1. If the number of running threads is less than corePoolSize, then immediately create a thread to run this task;
    2. If the number of running threads is greater than or equal to corePoolSize, then put the task in the queue.
    3. If the queue is full at this time, and the number of running threads is less than the maximumPoolSize, then you still have to create a thread to run this task;
    4. If the queue is full and the number of running threads is greater than or equal to maximumPoolSize, then the thread pool will throw an exception, telling the caller "I can no longer accept tasks."
  3. When a thread completes its task, it will remove the next task from the queue for execution.
  4. When a thread has nothing to do and exceeds a certain amount of time (keepAliveTime), the thread pool will judge that if the number of currently running threads is greater than corePoolSize, then this thread will be stopped. So after all tasks of the thread pool are completed, it will eventually shrink to the size of corePoolSize.



Executors

Tools

The essence of creating a thread pool is to generate a ThreadPoolExecutor, for example:

newFixedThreadPool

 

public static ExecutorService newSingleThreadExecutor() {

    return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));

}

 

newFixedThreadPool

 

public static ExecutorService newFixedThreadPool(int nThreads) {

    return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

}

 

newCachedThreadPool

 

public static ExecutorService newCachedThreadPool() {

    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

}

  • newCachedThreadPool The number of core threads is 0
    and the cacheable thread pool can be cached. If the length of the thread pool exceeds the processing requirement, idle threads can be recycled flexibly. If there is no recycling, a new thread can be created. The thread pool is Integer.MAX_VALUE size. When the second task is executed, the first task has been completed, and the thread executing the first task will be reused instead of creating a new thread each time.
  • newFixedThreadPool
    fixed-length thread pool, the number of core threads and the maximum number of threads are the same size, you can control the maximum concurrent number of threads, the excess threads will wait in the queue. The size of the fixed-length thread pool is best set according to system resources, such as Runtime.getRuntime().availableProcessors().
  • newSingleThreadExecutor
    creates a single-threaded thread pool. It will only use a single worker thread to execute tasks, ensuring that all tasks are executed in the specified order (FIFO, LIFO, priority).
  • newScheduledThreadPool
    creates a fixed-length thread pool to support timing and periodic task execution.
  • public class T_ScheduledPool {
  •  
  •     public static void main(String[] args) {
  •         ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
  •  
  •         service.scheduleAtFixedRate(()->{
  •  
  •             try {
  •                 TimeUnit.MILLISECONDS.sleep(new Random().nextInt());
  •             } catch (InterruptedException e) {
  •                 e.printStackTrace ();
  •             }
  •  
  •             System.out.println(Thread.currentThread().getName());
  •  
  •         },0,500, TimeUnit.MILLISECONDS);
  •     }
  • }
  • ScheduledExecutorService inherits ExecutorService

 

 

  • public class T_ScheduledPool {
  •  
  •     public static void main(String[] args) {
  •         ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
  •  
  •         service.scheduleAtFixedRate(()->{
  •  
  •             try {
  •                 TimeUnit.MILLISECONDS.sleep(new Random().nextInt());
  •             } catch (InterruptedException e) {
  •                 e.printStackTrace ();
  •             }
  •  
  •             System.out.println(Thread.currentThread().getName());
  •  
  •         },0,500, TimeUnit.MILLISECONDS);
  •     }
  • }
  • ScheduledExecutorService inherits ExecutorService

 

 

 

 

Guess you like

Origin blog.csdn.net/huzhiliayanghao/article/details/106816445