并发工具类:线程池execute和submit有什么区别

在这里插入图片描述

使用工具类创建线程池

上一节我们已经自己实现了一个线程池,本节我们看看JDK提供的线程池是如何实现的?

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

其实JDK提供的线程池创建和执行过程和我们的基本一样。你看构造函数都一摸一样

参数 含义
corePoolSize 核心线程数
maximumPoolSize 最大线程数
keepAliveTime 非核心线程的空闲时间
TimeUnit 空闲时间的单位
BlockingQueue<Runnable> 任务队列
ThreadFactory 线程工厂
RejectedExecutionHandler 拒绝策略

可能由于创建线程池太麻烦了,JDK提供了一个Executors工具类,帮我们快速创建各种各样的线程池

方法 特点
newCachedThreadPool 可缓存线程池,线程池长度超过处理需要,可回收线程,线程池为无限大,当执行第二个任务的时候,第一个任务已经完成,会复用第一个任务的线程,而不用重新创建
newFixedThreadPool 定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
newScheduledThreadPool 定长线程池,支持定时及周期性任务执行
newSingleThreadExecutor 单例线程池,用唯一的工作线程执行任务,保证所有任务按照指定顺序执行(FIFO或者LIFO)

根据描述你能猜一下他们构造函数中设置的7个属性分别是啥吗?

如果猜对了,那你对线程池已经掌握的很熟练了

我们来演示一下这几个方法

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();
    }
}

这个看起来好像没有用到线程池,其实是因为没有可复用的线程,所以就一直创建新的线程了

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();
    }
}

既然是工具类,肯定内置了各种参数的实现,比如,ThreadFactory(线程工厂类),RejectedExecutionHandler(拒绝策略)

先来看ThreadFactory的实现

在这里插入图片描述

设置了一下线程的名字和优先级等。

接着看RejectedExecutionHandler,Executors内置了四种实现

策略
AbortPolicy 丢弃任务,抛运行时异常(默认的处理策略)
CallerRunsPolicy 用放任务的线程执行任务(相当于就是同步执行了)
DiscardPolicy 忽视,什么都不会发生
DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务

在这里插入图片描述

执行任务

在这里插入图片描述

  1. 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行他们。
  2. 当调用execute()方法添加一个任务时,线程池会做如下判断:
    a)如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务
    b)如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列
    c)如果这时候队列满了,而且正在运行的线程数量小于maximunPoolSize,那么还是要创建非核心线程立刻运行这个任务
    d)如果队列满了,而且正在运行的线程数量大于或等于maximunPoolSize,那么线程池会根据拒绝策略来处理任务
  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行
  4. 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小

execute和submit方法有什么区别?

// 提交任务,不关心执行情况
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
}

原来用了适配器模式将Runnable接口适配为Callable接口

// 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;
}

使用线程池的好处

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

参考博客

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

猜你喜欢

转载自blog.csdn.net/zzti_erlie/article/details/124059864