Several ways of creating threads in Java

Posted by tjg73 on Mon, 03 Jan 2022 06:06:26 +0100

1, Inherit Thread class

The Thread class essentially implements an instance of the Runnable interface. It starts a new Thread by inheriting the Thread class and copying the run method. Calling the start method tells the CPU that the Thread is ready to execute, and then the system will execute its run method when it has time. Directly calling the run method is not asynchronous execution, but is equivalent to calling functions to execute synchronously in sequence, which loses the meaning of multithreading.

public class ThreadDemo1 extends Thread {

    public ThreadDemo1(String name) {
        // Sets the name of the current thread
        this.setName(name);
    }

    @Override
    public void run() {
        System.out.println("The currently running thread name is: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) throws Exception {
        // Note that the start method must be called to start the thread, and the run method cannot be called
        new ThreadDemo1("MyThread1").start();
        new ThreadDemo1("MyThread2").start();
    }

}

2, Implement Runnable interface

The Runnable interface is implemented and the run method is implemented. Then, by constructing Thread instances, Runnable is introduced into the implementation class, and then the start method of Thread is invoked to open a new thread. Inheriting thread class can only inherit single, while implementing runnable interface can realize multiple inheritance.

public class ThreadDemo2 implements Runnable {

    @Override
    public void run() {
        System.out.println("The currently running thread name is: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) throws Exception {
        ThreadDemo2 runnable = new ThreadDemo2();
        new Thread(runnable, "MyThread1").start();
        new Thread(runnable, "MyThread2").start();
    }

}

3, Creating threads through Callable and Future

In this case, the first two methods of the thread can not be used to get the value of the exception returned by the first two methods of the thread.

Callable is not a sub interface of Runnable, but a new interface. Its example cannot be constructed by passing in Thread.

In Java 1 In version 5, the Future interface is provided to represent the return value of the call() method in the Callable interface, and an implementation class FutureTask is provided for the Future interface. This implementation class implements not only the Future interface, but also the Runnable interface, so it can be directly passed to the Thread constructor.

Simulation scenario:

Start five threads at the same time and pass in the values 1 to 5 as the identification. If the passed in value is odd, execute for 3 seconds and even for 8 seconds, and each thread has a return result.

Now you need to output the return result of each thread. If the thread execution time exceeds 5 seconds, terminate the thread.

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.concurrent.*;


public class ThreadDemo3 {
    static Logger logger = LogManager.getLogger(ThreadTest.class);

    static class Task implements Callable {
        private int num;

        public Task (int num) {
            this.num = num;
        }

        @Override
        public Object call() throws Exception {
            logger.info(num+"Thread start");
            if((num&1)==1){
                Thread.sleep(3000);
            }else{
                Thread.sleep(8000);
            }
            return num+"Result returned by thread No";
        }
    }

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

        ExecutorService exec = Executors.newCachedThreadPool();
        ArrayList<Future<String>> resultList = new ArrayList<>();

        for(int i=1;i<6;i++){
            Callable<String> call = new Task(i);
            Future<String> future = exec.submit(call);
            resultList.add(future);
        }

        for(Future<String> future:resultList){
            try {
                String rt = future.get(5, TimeUnit.SECONDS);
                logger.info(rt);
            } catch (TimeoutException e){
                future.cancel(true); //Unable to stop the current thread for query data class operation
                logger.error("Thread timeout aborted");
            } catch (ExecutionException ex){
                ex.printStackTrace();
            }
        }

        exec.shutdown();
        logger.info("Main thread end");
    }
    
}

Output results:

2021-07-30 08:23:29: thread 2 started
2021-07-30 08:23:29: thread 1 started
2021-07-30 08:23:29: thread 5 started
2021-07-30 08:23:29: thread 4 started
2021-07-30 08:23:29: thread 3 started
2021-07-30 08:23:32: result returned by thread 1
2021-07-30 08:23:37: result returned by thread 2
2021-07-30 08:23:37: result returned by thread 3
2021-07-30 08:23:37: result returned by thread 4
2021-07-30 08:23:37: result returned by thread 5
2021-07-30 08:23:37: end of main thread

Result description:

According to the five threads started on demand, No. 2 and No. 4 are even and the execution time exceeds 5 seconds. They should be terminated, but the results are output normally.

The reason is that the get method will block the calling thread. While blocking, other thread tasks are still running. Thread 1 is blocked for 3 seconds. At this time, when you go to cycle to obtain the results of thread 2, thread 2 and 4 have actually been executed in the background for 3 seconds, and it takes another 5 seconds to return the results. The set timeout time is just 5 seconds, and there is no timeout, So the results are all output. The effect of asynchrony will become invalid when we use get to get results.

To solve the above problems, Java 1 8. A newly added implementation class completable Future implements two interfaces: Future < T > and completionstage < T > for Future completion events without blocking and waiting. During this period, thread tasks can continue to execute normally. Completable Future can put the callback into a thread different from the task for execution, or take the callback as a synchronization function to continue execution and execute in the same thread as the task.

Use completabilefuture Supplyasync rewrites the above requirement Code:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.concurrent.*;


public class ThreadDemo4 {
    static Logger logger = LogManager.getLogger(ThreadTest.class);

    static class Task implements Callable {
		//do something...
    }

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

        ExecutorService exec = Executors.newCachedThreadPool();
        ArrayList<CompletableFuture<String>> resultList = new ArrayList<>();

        for(int i=1;i<6;i++){
            int finalI = i;
            CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
                String result =null;
                Task task = new Task(finalI);
                Future<String> ft = exec.submit(task);
                try {
                    result = ft.get(5, TimeUnit.SECONDS);
                } catch (TimeoutException e) {
                    ft.cancel(true); //Unable to stop the current thread for query data class operation
                    return finalI+"Thread number 1 timed out and aborted";
                } catch (ExecutionException | InterruptedException e) {
                    e.printStackTrace();
                }
                return result;
            });
            resultList.add(future);
        }

        for(CompletableFuture<String> future:resultList){
            String ret = null;
            try {
                ret = future.get();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            logger.info(ret);
        }

        exec.shutdown();
        logger.info("Main thread end");
    }

}

Output results:

2021-07-30 08:44:09: thread 5 started
2021-07-30 08:44:09: thread 4 started
2021-07-30 08:44:09: thread 2 started
2021-07-30 08:44:09: thread 1 started
2021-07-30 08:44:09: thread 3 started
2021-07-30 08:44:12: result returned by thread 1
2021-07-30 08:44:14: thread 2 timed out and aborted
2021-07-30 08:44:14: result returned by thread 3
2021-07-30 08:44:14: thread 4 timed out and aborted
2021-07-30 08:44:14: result returned by thread 5
2021-07-30 08:44:14: end of main thread

Result description:

The output reached the required effect. Threads 2 and 4 timed out and terminated, and other threads output normally. The reason is that the original call to Future Get the blocked part and put it into completable Future The supplyasync method is executed asynchronously, which makes up for the shortcomings of the Future pattern. After an asynchronous task is completed, you do not need to wait when you need to continue with its results.

4, Creating threads from a thread pool

Compared with creating a new Thread object, the thread pool can be better managed uniformly, reduce the creation / destruction / overhead of objects, and each working thread can be reused. According to the actual requirements, it can effectively control the number of concurrency and improve the utilization rate of system resources.

Executors tool class

Java provides factory methods through the Executors tool class to create different types of thread pools:

Creation methodexplain
newCachedThreadPoolCreate a cacheable thread pool. If the length of the thread pool exceeds the processing needs, you can flexibly recycle idle threads. If there is no recyclable thread, you can create a new thread.
newFixedThreadPoolCreate a fixed length thread pool to control the maximum concurrent number of threads. The exceeded threads will wait in the queue.
newSingleThreadExecutorCreate a fixed length routing pool to support regular and periodic task execution. Create a singleton thread pool. It will only use a unique worker thread to execute tasks to ensure that all tasks are executed in the specified order (FIFO, LIFO, priority).
newScheduledThreadPoolCreate a fixed length routing pool to support regular and periodic task execution.

Executors method to create thread pool:

// Cache thread pool. The number of thread pools is not fixed and can be changed automatically according to demand
ExecutorService pool = Executors.newCachedThreadPool();
// Create a fixed size thread pool
ExecutorService pool = Executors.newFixedThreadPool();
// Create a single thread pool. There is only one thread in the thread pool
ExecutorService pool = Executors.newSingleThreadExecutor();

// Create a fixed size thread that can delay or schedule the execution of tasks
ScheduledExecutorService pool = Executors.newScheduledThreadPool();

Executor and ExecutorService are interfaces. ExecutorService inherits from executor. Executors is a tool class that provides encapsulation of ThreadPoolExecutor.

The ExecutorService interface provides methods such as returning the Future object, terminating and closing the thread pool. When shutdown() is called, the thread pool will stop accepting new tasks, but will complete the pending tasks. The ThreadDemo3 and ThreadDemo4 examples in this article also use this method to create threads.

newCachedThreadPool source code

/**
@param corePoolSize=0: Unlimited number of threads
@param maximumPoolSize=Integer.MAX_VALUE: The maximum number of threads is the maximum value of Interger
@param keepAliveTime=60: The thread ends automatically after it is idle for 60s
@param TimeUnit=TimeUnit.SECONDS: Unit second
@param workQueue=SynchronousQueue: Synchronize the queue, one in and one out, to avoid buffering data in the queue. Incoming and outgoing data must be transmitted at the same time
*/

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

newFixedThreadPool source code

/**
@param corePoolSize=nThreads: int Type parameter, thread pool size
@param maximumPoolSize=nThreads: All threads are core threads, equal to corePoolSize
@param keepAliveTime=0: This parameter is not valid for core threads by default
@param TimeUnit=TimeUnit.MILLISECONDS: 
@param workQueue=LinkedBlockingQueue: Unbounded blocking queue. The maximum value of the queue is integer MAX_ Value: if the task submission speed is faster than the execution speed, it will cause a lot of blocking and eventually lead to memory overflow
*/

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

newSingleThreadExecutor source code

/**
@param corePoolSize=1: Fixed single thread
@param maximumPoolSize=1: Fixed single thread
@param keepAliveTime=0: This parameter is not valid for core threads by default
@param TimeUnit=TimeUnit.MILLISECONDS: 
@param workQueue=LinkedBlockingQueue: Unbounded blocking queue. The maximum value of the queue is integer MAX_ VALUE
*/

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

newScheduledThreadPool source code

/**
Description: newScheduledThreadPool calls the construction method of ScheduledThreadPoolExecutor, and ScheduledThreadPoolExecutor inherits ThreadPoolExecutor

@param corePoolSize=int corePoolSize: Thread pool size
@param maximumPoolSize=Integer.MAX_VALUE: Maximum value of shaping
@param workQueue=DelayedWorkQueue: Customized priority queue, based on the implementation of heap data structure
*/

public static ScheduledExecutorService newScheduledThreadPool(
    int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory);
}

ThreadPoolExecutor object

When all threads in the ExecutorService thread pool are working and the number of threads has reached the maximum number of threads allowed by the thread pool, the specified saturation policy will be adopted to process the newly submitted task.

There are four strategies:

  1. AbortPolicy: the default policy.
    The rejectexecutor will throw an exception to the caller to complete the execution.
  2. CallerRunsPolicy (caller thread to run the task):
    This strategy will be executed by the thread calling the execute method itself. It provides a simple feedback mechanism and can reduce the submission frequency of new tasks.
  3. Discard policy: discard the submitted task without processing.
  4. DiscardOldestPolicy (discard the latest task in the queue):

If the Executor has not shut down (), the latest task of the work queue is discarded and the current task is executed.

If the thread pool is created using the factory method of Executors, the default AbortPolicy is adopted for the saturation policy. Therefore, if we want to use the caller's thread to run the task when the thread pool is full, we need to create the thread pool ourselves and specify the desired saturation policy instead of using Executors.

Therefore, we can create ThreadPoolExecutor (implementation class of executorservice interface) objects as needed and customize some parameters instead of calling the factory method of Executors.

/**
@param corePoolSize: Thread pool size
@param maximumPoolSize: The maximum number of threads in the thread pool. The maximum number of threads that the thread pool will open up is determined according to the type of workQueue task queue
@param keepAliveTime: When the number of free threads in the thread pool exceeds the corePoolSize, how long will the redundant threads be destroyed
@param unit: keepAliveTime Unit of
@param workQueue: Task queue, tasks added to the thread pool but not yet executed
@param threadFactory: Thread factory is used to create threads. Generally, it can be used by default
@param handler: Rejection strategy;
*/

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

//Example
ExecutorService exec = new ThreadPoolExecutor(coreSize, maxCoreSize, aliveTime, timeUnit, queue);

Topics: Java Multithreading thread thread pool future