Concurrency tool class: what is the difference between thread pool execute and submit

insert image description here

Use utility classes to create thread pools

In the previous section, we have implemented a thread pool ourselves. In this section, let's see how the thread pool provided by JDK is implemented?

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

In fact, the thread pool creation and execution process provided by JDK is basically the same as ours. You see the constructors are all the same

parameter meaning
corePoolSize number of core threads
maximumPoolSize maximum number of threads
keepAliveTime Idle time of non-core threads
TimeUnit unit of idle time
BlockingQueue<Runnable> task queue
ThreadFactory thread factory
RejectedExecutionHandler rejection policy

Maybe because it is too troublesome to create a thread pool, JDK provides an Executors tool class to help us quickly create a variety of thread pools

method Features
newCachedThreadPool Cacheable thread pool, the length of the thread pool exceeds the processing needs, the thread can be recycled, and the thread pool is infinite. When the second task is executed, the first task has been completed, and the thread of the first task will be reused instead of rebuild
newFixedThreadPool Fixed-length thread pool, which can control the maximum number of concurrent threads, and the excess threads will wait in the queue
newScheduledThreadPool Fixed-length thread pool, supports timing and periodic task execution
newSingleThreadExecutor A singleton thread pool that executes tasks with a unique worker thread to ensure that all tasks are executed in the specified order (FIFO or LIFO)

According to the description, can you guess what the 7 properties are set in their constructors?

If you guessed it right, you're pretty familiar with thread pools.

Let's demonstrate these methods

public class Task extends Thread{
    
    

    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName() + " is running");
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}
public class TestCachedThreadPool {
    
    

    public static void main(String[] args) {
    
    
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
    
    
            Task task = new Task();
            executorService.execute(task);
        }
        //pool-1-thread-1 is running
        //pool-1-thread-5 is running
        //pool-1-thread-2 is running
        //pool-1-thread-4 is running
        //pool-1-thread-3 is running
        //必须显式结束,不然程序永远不会结束
        executorService.shutdown();
    }
}

This does not seem to use the thread pool, but in fact, because there are no reusable threads, new threads are always created

public class TestFixedThreadPool {
    
    

    public static void main(String[] args) {
    
    
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 5; i++) {
    
    
            Task task = new Task();
            executorService.execute(task);
        }
        //pool-1-thread-1 is running
        //pool-1-thread-2 is running
        //pool-1-thread-1 is running
        //pool-1-thread-2 is running
        //pool-1-thread-1 is running
        executorService.shutdown();
    }
}
public class TestScheduledThreadPool {
    
    

    public static void main(String[] args) {
    
    
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
        //任务,第1次任务延迟的时间,2次任务间隔时间,时间单位
        executorService.scheduleAtFixedRate(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("task 1 " + System.currentTimeMillis());
            }
        }, 1, 5, TimeUnit.SECONDS);
        //两者互不影响
        executorService.scheduleAtFixedRate(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("task 2 " + System.currentTimeMillis());
            }
        }, 1, 2,TimeUnit.SECONDS);
        //task 1 1521949174111
        //task 2 1521949174112
        //task 2 1521949176106
        //task 2 1521949178122
        //task 1 1521949179104
        //task 2 1521949180114
    }
}
public class TestSingleThreadExecutor {
    
    

    public static void main(String[] args) {
    
    
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
    
    
            Task task = new Task();
            executorService.execute(task);
        }
        //pool-1-thread-1 is running
        //pool-1-thread-1 is running
        //pool-1-thread-1 is running
        //pool-1-thread-1 is running
        //pool-1-thread-1 is running
        executorService.shutdown();
    }
}

Since it is a tool class, it must have built-in implementations of various parameters, such as ThreadFactory (thread factory class), RejectedExecutionHandler (rejection strategy)

First look at the implementation of ThreadFactory

insert image description here

Set the thread name and priority.

Then look at RejectedExecutionHandler, Executors have four built-in implementations

kind Strategy
AbortPolicy Discard the task and throw a runtime exception (default processing strategy)
CallerRunsPolicy Execute the task with the thread that puts the task (equivalent to synchronous execution)
DiscardPolicy Ignore, nothing will happen
DiscardOldestPolicy Discard the most recent task in the queue and execute the current task

insert image description here

perform tasks

insert image description here

  1. When the thread pool is just created, there is 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:
    a) If the number of running threads is less than corePoolSize, then create a thread to run the task immediately
    b) If the number of running threads is greater than or equal to corePoolSize, then Put this task into the queue
    c) If the queue is full at this time, and the number of running threads is less than maximunPoolSize, then create a non-core thread to run the task immediately
    d) If the queue is full, and the number of running threads is greater than or equal to maximunPoolSize, then the thread pool will process tasks according to the rejection policy
  3. When a thread completes a task, it takes the next task from the queue to execute
  4. When a thread has nothing to do and exceeds a certain time (keepAliveTime), the thread pool will judge that if the number of currently running threads is greater than corePoolSize, the thread will be stopped. So after all tasks of the thread pool are done, it will eventually shrink to the size of corePoolSize

What is the difference between execute and submit methods?

// 提交任务,不关心执行情况
public void execute(Runnable command);

// 提交任务,get的时候如果线程执行成功返回null,否则获取到异常的信息
public Future<?> submit(Runnable task);

// 提交任务,get的时候如果线程执行成功返回result,否则获取到异常的信息
public <T> Future<T> submit(Runnable task, T result);

// 提交任务,get的时候如果线程执行成功返回Callable返回的值,否则获取到异常信息
public <T> Future<T> submit(Callable<T> task);
// ThreadPoolExecutor
public void execute(Runnable command) {
    
    
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 当前线程数 < corePoolSize
    if (workerCountOf(c) < corePoolSize) {
    
    
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 放入队列成功
    if (isRunning(c) && workQueue.offer(command)) {
    
    
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 创建线程失败,执行拒绝策略
    else if (!addWorker(command, false))
        reject(command);
}
// AbstractExecutorService
public Future<?> submit(Runnable task) {
    
    
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

// AbstractExecutorService
public <T> Future<T> submit(Runnable task, T result) {
    
    
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task, result);
    execute(ftask);
    return ftask;
}
// AbstractExecutorService
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    
    
    return new FutureTask<T>(callable);
}
public FutureTask(Runnable runnable, V result) {
    
    
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

It turned out that the adapter mode was used to adapt the Runnable interface to the Callable interface

// Executors
static final class RunnableAdapter<T> implements Callable<T> {
    
    
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
    
    
        this.task = task;
        this.result = result;
    }
    public T call() {
    
    
        task.run();
        return result;
    }
}
public <T> Future<T> submit(Callable<T> task) {
    
    
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

The benefits of using thread pools

  1. Reduce resource consumption. Reduce the cost of thread creation and destruction by reusing already created threads
  2. Improve responsiveness. When the task arrives, the task can be executed immediately without waiting until the thread is created
  3. Improve thread manageability. Threads are scarce resources. If they are created without restrictions, it will not only consume system resources, but also reduce the stability of the system. Using thread pools can be used for unified allocation, tuning and monitoring.

Reference blog

[1]https://blog.csdn.net/guhong5153/article/details/71247266

Guess you like

Origin blog.csdn.net/zzti_erlie/article/details/124059864