0
点赞
收藏
分享

微信扫一扫

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


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

使用工具类创建线程池

上一节我们已经自己实现了一个线程池,本节我们看看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的实现

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

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

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


策略

AbortPolicy

丢弃任务,抛运行时异常(默认的处理策略)

CallerRunsPolicy

用放任务的线程执行任务(相当于就是同步执行了)

DiscardPolicy

忽视,什么都不会发生

DiscardOldestPolicy

丢弃队列里最近的一个任务,并执行当前任务

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

执行任务

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

  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. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

举报

相关推荐

0 条评论