java线程池的使用

本文转载于以下文章,原文链接如下:
为什么需要线程池:https://www.cnblogs.com/ljp-sun/p/6580147.html
Java中的ThreadPoolExecutor类:https://blog.csdn.net/yinni11/article/details/79917640
四种线程池的使用示例:https://blog.csdn.net/u010476994/article/details/107516635
四种线程池的运用场景:https://blog.csdn.net/fuyuwei2015/article/details/72775426
线程池的四种拒绝策略:https://blog.csdn.net/suifeng629/article/details/98884972
线程池的submit和execute方法区别:https://blog.csdn.net/u010458765/article/details/53063762

前言

为什么需要线程池?
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。
假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。

如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。

线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。
线程池不仅调整T1,T3产生的时间段,而且它还显著减少了创建线程的数目,看一个例子:
假设一个服务器一天要处理50000个请求,并且每个请求需要一个单独的线程完成。在线程池中,线程数一般是固定的,所以产生线程总数不会超过线程池中线程的数目,而如果服务器不利用线程池来处理这些请求则线程总数为50000。一般线程池大小是远小于50000。所以利用线程池的服务器程序不会为了创建50000而在处理请求时浪费时间,从而提高效率。
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

线程池的作用总结为:

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

一.Java中的ThreadPoolExecutor类

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。

在ThreadPoolExecutor类中提供了四个构造方法:

public class ThreadPoolExecutorextends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

下面解释下一下构造器中各个参数的含义:

corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒

workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。

threadFactory:线程工厂,主要用来创建线程;
handler:表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
  具体参数的配置与线程池的关系将在下一节讲述。

从上面给出的ThreadPoolExecutor类的代码可以知道,ThreadPoolExecutor继承了AbstractExecutorService,我们来看一下AbstractExecutorService的实现:

public abstract class AbstractExecutorServiceimplements ExecutorService {
 
     
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { };
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { };
    public Future<?> submit(Runnable task) {};
    public <T> Future<T> submit(Runnable task, T result) { };
    public <T> Future<T> submit(Callable<T> task) { };
    private <T> T doInvokeAny(Collection<?extends Callable<T>> tasks,
                            boolean timed,long nanos)
        throws InterruptedException, ExecutionException, TimeoutException {
    };
    public <T> T invokeAny(Collection<?extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException {
    };
    public <T> T invokeAny(Collection<?extends Callable<T>> tasks,
                           long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
    };
    public <T> List<Future<T>> invokeAll(Collection<?extends Callable<T>> tasks)
        throws InterruptedException {
    };
    public <T> List<Future<T>> invokeAll(Collection<?extends Callable<T>> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
    };
}

AbstractExecutorService是一个抽象类,它实现了ExecutorService接口。

我们接着看ExecutorService接口的实现:

public interface ExecutorService extends Executor {
 
    void shutdown();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<?extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<?extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
 
    <T> T invokeAny(Collection<?extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<?extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

而ExecutorService又是继承了Executor接口,我们看一下Executor接口的实现:

public interface Executor {
    void execute(Runnable command);
}

到这里,大家应该明白了ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor几个之间的关系了。
 在这里插入图片描述

Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;

然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;

抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;

然后ThreadPoolExecutor继承了类AbstractExecutorService。

在ThreadPoolExecutor类中有几个非常重要的方法:

execute()
submit()
shutdown()
shutdownNow()

execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。

shutdown()和shutdownNow()是用来关闭线程池的。

还有很多其他的方法:

比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法,有兴趣的朋友可以自行查阅API。

二、四种线程池的使用

FixedThreadPool

创建一个具有固定线程数的线程池,当创建的线程达到最大数量后,就不再创建新的线程。当所有的线程都在使用时,新提交的任务会阻塞在队列中,直至有线程空闲出来。

package com.dfire.monitor.service.impl.poolTest;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author : baigp
 * create at:  2020/7/22  15:49
 * @description:
 */
public class FixedThreadPoolTest {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Runnable run = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("当前线程名称:" + Thread.currentThread().getName() + " , 当前时间为:" + sdf.format(new Date()));
                    Thread.sleep(3 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        for (int i = 0 ; i < 10 ; i++){
            executorService.execute(run);
        }
    }
}

打印结果:

当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:14:23
当前线程名称:pool-1-thread-2 , 当前时间为:2020-07-22 16:14:23
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:14:26
当前线程名称:pool-1-thread-2 , 当前时间为:2020-07-22 16:14:26
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:14:29
当前线程名称:pool-1-thread-2 , 当前时间为:2020-07-22 16:14:29
当前线程名称:pool-1-thread-2 , 当前时间为:2020-07-22 16:14:32
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:14:32
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:14:35
当前线程名称:pool-1-thread-2 , 当前时间为:2020-07-22 16:14:35
————————————————
从打印结果可以看出,一共创建了2个线程,和代码汇总配置的线程数相对应。第三、四个任务提交时,由于两个线程被占用着,所以阻塞在队列中,直至第一、二个任务休眠三秒后,释放了线程,第三、四个任务才开始执行。

使用场景
FixedThreadPool适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器

SingleThreadExecutor

创建使用单个线程的SingleThread-Executor

package com.dfire.monitor.service.impl.poolTest;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author : baigp
 * create at:  2020/7/22  16:03
 * @description:
 */
public class SingleThreadExecutorTest {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Runnable run = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("当前线程名称:" + Thread.currentThread().getName() + " , 当前时间为:" + sdf.format(new Date()));
                    Thread.sleep(3 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        ExecutorService executroService = Executors.newSingleThreadExecutor();
        for (int i = 0 ; i < 10; i++){
            executroService.execute(run);
        }
    }
}

打印结果:

当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:26:19
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:26:22
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:26:25
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:26:28
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:26:31
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:26:34
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:26:37
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:26:40
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:26:43
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 16:26:46
————————————————
可以看出,自始至终只创建了一个线程,后面的任务等待前面的任务执行完毕后才开始执行。

使用场景
SingleThreadExecutor适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。

CachedThreadPool

可缓冲线程池。特点:灵活。 没有线程最大数量的限制,当一个任务被提交到线程池时,如果有闲置的线程,则选取一个闲置线程执行,如果没有闲置线程,则创建一个新线程。 对于线程池中的线程,闲置60s后会自动销毁以节省资源。

package com.dfire.monitor.service.impl.poolTest;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author : baigp
 * create at:  2020/7/22  16:30
 * @description:
 */
public class CachedThreadPoolTest {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Runnable run = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("当前线程名称:" + Thread.currentThread().getName() + " , 当前时间为:" + sdf.format(new Date()));
                    Thread.sleep(3 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0 ; i < 1000 ; i++){
            executorService.execute(run);
        }
    }
}

————————————————
打印结果:

当前线程名称:pool-1-thread-5 , 当前时间为:2020-07-22 16:32:21
当前线程名称:pool-1-thread-6 , 当前时间为:2020-07-22 16:32:21
当前线程名称:pool-1-thread-4 , 当前时间为:2020-07-22 16:32:21
当前线程名称:pool-1-thread-2 , 当前时间为:2020-07-22 16:32:21
当前线程名称:pool-1-thread-8 , 当前时间为:2020-07-22 16:32:21
省略中间的打印内容…
当前线程名称:pool-1-thread-997 , 当前时间为:2020-07-22 16:32:21
当前线程名称:pool-1-thread-998 , 当前时间为:2020-07-22 16:32:21
当前线程名称:pool-1-thread-999 , 当前时间为:2020-07-22 16:32:21
当前线程名称:pool-1-thread-1000 , 当前时间为:2020-07-22 16:32:21
————————————————
使用场景
看名字我们可以知道cached缓存,CachedThreadPool可以创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。
CachedThreadPool是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。

ScheduledThreadPool

固定线程数上限的线程池,并支持定时和周期性执行任务。

package com.dfire.monitor.service.impl.poolTest;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;

/**
 * @author : baigp
 * create at:  2020/7/22  17:11
 * @description:
 */
public class ScheduledThreadPoolTest {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Runnable run = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("当前线程名称:" + Thread.currentThread().getName() + " , 当前时间为:" + sdf.format(new Date()));
                    Thread.sleep(3 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
        System.out.println("开始执行时间:" + sdf.format(new Date()));
        executorService.scheduleAtFixedRate(run , 3, 5 , TimeUnit.SECONDS);
    }
}

————————————————
打印结果:

开始执行时间:2020-07-22 17:17:57
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 17:18:00
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 17:18:05
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 17:18:10
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 17:18:15
————————————————

可以看出,executorService 添加线程任务后,首先延迟3s,然后执行run(),而后按照5s/次的频率定时执行run()方法。
注意:如果执行频率由5s/次加快到2s/次,run()方法执行一次需要3s(休眠3s),那么后面的任务会阻塞,等前面的任务执行完后再执行,所用的线程自始至终都是同一个。
执行频率由5s/次加快到2s/次后的打印结果:

开始执行时间:2020-07-22 17:22:47
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 17:22:50
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 17:22:53
当前线程名称:pool-1-thread-1 , 当前时间为:2020-07-22 17:22:56
————————————————
ScheduledThreadPool适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景。

三、线程池拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略。
通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务 。
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。

AbortPolicy

java.util.concurrent.ThreadPoolExecutor类的源码,我们可以看到:

/**
 * The default rejected execution handler
 */
private static final RejectedExecutionHandler defaultHandler =
 new AbortPolicy();

线程池的默认拒绝策略为AbortPolicy,即丢弃任务并抛出RejectedExecutionException异常。我们可以通过代码来验证这一点,现有如下代码:

public class ThreadPoolTest {
	public static void main(String[] args) {
		BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
		ThreadFactory factory = r -> new Thread(r, "test-thread-pool");
		ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,0L, TimeUnit.SECONDS, queue, factory);
		while (true) {
			executor.submit(() -> {
				try {
					System.out.println(queue.size());
					Thread.sleep(10000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			});
		}
	}
}

这里是一个默认的线程池,没有设置拒绝策略,设置了最大线程队列是100。运行代码:
在这里插入图片描述
这也证明了线程池的默认拒绝策略是ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

运用场景
这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。

DiscardPolicy

ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。

A handler for rejected tasks that silently discards therejected task.
使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。例如,本人的博客网站统计阅读量就是采用的这种拒绝策略。

DiscardOldestPolicy

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。

A handler for rejected tasks that discards the oldest unhandled request and then retries {@code execute}, unless the executor is shut down, in which case the task is discarded.
此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。

CallerRunsPolicy

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

A handler for rejected tasks that runs the rejected task directly in the calling thread of the {@code execute} method, unless the executor has been shut down, in which case the task is discarded.

如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务,我们可以通过代码来验证这一点:

把之前的代码修改如下:

public static void main(String[] args) {
	BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);
 	ThreadFactory factory = r -> new Thread(r, "test-thread-pool");
 	ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS, queue, factory, new ThreadPoolExecutor.CallerRunsPolicy());
 	for (int i = 0; i < 1000; i++) {
 		executor.submit(() -> {
 			try {
 				System.out.println(Thread.currentThread().getName() + ":执行任务");
 				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
 			}
 		});
 	}
}

把队列最大值改为10,打印输出线程的名称。执行结果如下:
在这里插入图片描述
通过结果可以看到,主线程main也执行了任务,这正说明了此拒绝策略由调用线程(提交任务的线程)直接执行被丢弃的任务的。

四、设置线程池拒绝策略

如果我们想要根据实际业务场景需要,设置其他的线程池拒绝策略,可以通过ThreadPoolExecutor重载的构造方法进行设置:
在这里插入图片描述
现在的开发中,相信大家都有使用spring,其实我们也可以通过spring提供的org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor构建线程池。如下:
在这里插入图片描述

五、线程池的submit和execute方法

线程池中的execute方法大家都不陌生,即开启线程执行池中的任务。还有一个方法submit也可以做到,它的功能是提交指定的任务去执行并且返回Future对象,即执行的结果。下面简要介绍一下两者的三个区别:

1、接收的参数不一样

2、submit有返回值,而execute没有

3、submit方便Exception处理

execute只能提交Runnable类型的任务,无返回值。submit既可以提交Runnable类型的任务,也可以提交Callable类型的任务,会有一个类型为Future的返回值,但当任务类型为Runnable时,返回值为null。
execute在执行任务时,如果遇到异常会直接抛出,而submit不会直接抛出,只有在使用Future的get方法获取返回值时,才会抛出异常。

下面一个小程序演示一下submit方法

public class RunnableTestMain {

    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);
        
        /**
         * execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。
         */
        pool.execute(new RunnableTest("Task1")); 
        
        /**
         * submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。请看下面:
         */
        Future future = pool.submit(new RunnableTest("Task2"));
        
        try {
            if(future.get()==null){//如果Future's get返回null,任务完成
                System.out.println("任务完成");
            }
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
            //否则我们可以看看任务失败的原因是什么
            System.out.println(e.getCause().getMessage());
        }

    }

}

public class RunnableTest implements Runnable {
    
    private String taskName;
    
    public RunnableTest(final String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        System.out.println("Inside "+taskName);
        throw new RuntimeException("RuntimeException from inside " + taskName);
    }

}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值