How threads are created

Thread

We start a Thread by creating a subclass of new Thread and calling its start method

public class MyThread extends Thread {
    @Override
    public void run() {
        ...
    }
}

public static void main(String[] args) {
    new MyThread().start();
}

Question: why is the start method called instead of the run method

1. When a new Thread is created, the Thread enters the new state

2. At this time, calling start will start a thread and make the thread enter the ready state. When the time slice is allocated, the thread can start running. Start performs the corresponding preparations for the thread, and then automatically executes the run method

3. However, if the run method is executed directly, it will be executed as a common method under the main thread, and it will not be executed in a thread, so this is not multi-threaded work

public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {

        }
    }
}

It is a synchronous method. It will call the start0 method. This method is a native method. Through this method, the virtual machine will run threads for us

Runnable

In Thread, there are two construction methods

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

We can pass in a Runnable interface to create a thread

public class MyRunnableThread implements Runnable {
    @Override
    public void run() { 
    	...
    }
}
public static void main(String[] args) {
    new Thread(new MyRunnableThread()).start();
}

Simplify with anonymous inner classes and Lambda expressions:

public static void main(String[] args) {
    // Optimization 1
    new Thread(new Runnable() {
        @Override
        public void run() {
            ...
        }
    }).start();
	    
    // Optimization 2
    new Thread(() -> {
        ...
    }).start();
}

Advantages of using this method to create threads: the interface is flexible and convenient, and an object can be shared by multiple threads

Underlying principle: static proxy mode

Callable

Callable is a method of creating threads introduced by J.U.C

Question: what is the difference between Callable and Runnable

1) Callable can obtain the return value through call, and Runnable needs to obtain it through shared variables

2) call can throw exceptions, and Runnable can only catch child thread exceptions in the main thread by setDefaultUncaughtExceptionHandler()

In the system of Runnable, Runnable has a subclass interface: RunnableFuture

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

RunnableFuture has an implementation class:

public class FutureTask<V> implements RunnableFuture<V> {
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
    ...
}

In this class, you can pass in a Callable interface to create an object. This object can be passed in Thread as the implementation of the Runnable interface to create a Thread

Simply put: connect Callable and Runnable through FutureTask

Create a Callable interface, display and define the return value type, override call and throw an exception

Implement the Callable interface, set the return value type, override the call method and throw an exception

class MyCallableThread implemets Callable<Boolean> {
    @Override
    public Integer call() throws Exception {
        return 1024;
    }
}

public static void main(String[] args) {
	FutureTask<Integer> future = new FutureTask<>(new MyCallableThread());
    new Thread(future).start();
    
    // Get return result
    Integer o = future.get(); 
}

Thread pool

Through the thread pool, we can create multiple threads in advance and put them in it. We can get them directly from the pool when we use them up and put them back into the pool. We can save a lot of time under concurrency

Advantages of using thread pool:

  1. Improve response speed and reduce the time to create new threads
  2. Reduce resource consumption and reuse threads in the thread pool
  3. Easy thread management

Main functions of thread pool:

  1. Reuse threads
  2. Control maximum concurrency
  3. Manage threads

Creation of thread pool

The Executors tool class can be used to create thread pools (not recommended)

    Executors.newSingleThreadExecutor();  // Single instance thread
    Executors.newFixedThreadPool(5);	  // Create a fixed size thread pool
    Executors.newCachedThreadPool(); 	  // Scalable
    ...

In J.U.C, there is an Executor interface, which is similar to Collection. It has a subclass interface: ExecutorService. The type of thread pool created is ExecutorService

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

    ExecutorService threadPool = Executors.newSingleThreadExecutor();

    // Use threads in the thread pool
    threadPool.execute(() -> {
        System.out.println(Thread.currentThread().getName());
    });

    // Close thread pool
    threadPool.shutdown();
}

Custom thread pool

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

When using the Executors tool class to create a thread pool, the new ThreadPoolExecutor is used to create a thread pool

Generally, we use this method to customize the thread pool:

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

Description of seven parameters:

parameterexplain
corePoolSizeNumber of core threads
maximumPoolSizeMaximum number of threads
keepAliveTimeIdle thread lifetime
TimeUnit unitTime unit
BlockingQueue workQueueBlocking queue
ThreadFactory threadFactoryThread factory, used to create threads
RejectedExecutionHandler handleRejection policy, the rejection method when the blocking queue is full

Presentation Description:

public static void main(String[] args) throws InterruptedException {
    ExecutorService threadPool = new ThreadPoolExecutor(
        2, 
        5,
        3,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<>(3), // The blocking queue size needs to be set
        Executors.defaultThreadFactory(), // Default g
        new ThreadPoolExecutor.AbortPolicy // Reject policy, throw exception
    );

    // Maximum bearer: the value of blocking queue + max. if the maximum bearer is exceeded, the rejection policy is used
    for (int i = 0; i < 9; i++) {
        threadPool.execute(() -> {
            System.out.println(Thread.currentThread().getName() + "ok");
        });
    }
    threadPool.shutdown();
}

Reject policy

When customizing the thread pool, set the maximum number of threads in the thread pool and a fixed size blocking queue

When all threads in the thread pool are working and the blocking queue is full, if there are still threads to be started, you need to use the deny policy to deny access to the thread pool

Four rejection strategies:

  • AbortPolicy: throw exception RejectedExecutionException
  • CallerRunsPolicy: returns the task to the caller
  • DiscardPolicy: discards the task that cannot be processed, does not process it, and does not throw an exception
  • DiscardOldestPolicy: discards the longest waiting task in the queue, adds the current task to the queue, and tries to submit the current task again

Setting of the maximum number of threads

Question: how should I customize the maximum number of threads in the thread pool

Confirm according to the type of task

  • CPU intensive: CPU cores + 1
  • IO intensive: 2 * CPU cores
Runtime().getRuntime().availableProcessors(); // Get the number of CPU cores

Tags: Java Multithreading Concurrent Programming thread pool

Posted on Sun, 12 Sep 2021 21:59:59 -0400 by Daggeth