Executors框架

1 前言

  通常java最简单的线程的例子是这样的:

    public static void main(String[] args) {
        Runnable runnable = () -> System.out.println("Thread is running.");
        Thread thread = new Thread(runnable);
        thread.start();
    }

        在小的示例程序中如上述实践是可以的;但在大规模的应用中将线程的管理和创建应用的其它部分分开则是更合理的实践。封装了线程的管理和创建函数的对象就是executors,可以提高管理效率和降低线程反复创建和销毁带来的开销。

线程的创建因为涉及到和操作系统的交互所以开销会比较大

  那么封装了线程管理和创建这些功能的对象就是 java.util.concurrent.Executors

2 Executor线程池

  Executor就是java.util.concurrent.Executors通过上述红框中几个静态方法创建的执行器,即线程池,线程池就是用于执行提交的Runnable任务。该接口将任务的提交和任务如何被执行(包括线程的使用细节、任务排程等)解耦。Executor线程池的使用过程中不需要显示的创建线程。你不再需要像 new Thread(new(RunnableTask())).start()一样为任务集合中的每一个任务创建线程来执行任务,线程池的使用如下:

Executor executor = anExecutor;

executor.execute(new RunnableTask1());

executor.execute(new RunnableTask2());

2.1 newFixedThreadPool

Executors.newFixedThreadPool(10);

2.2 newSingleThreadExecutor

Executors.newSingleThreadExecutor();

2.3 newCachedThreadPool

Executors.newCachedThreadPool();

2.4 手动创建线程池

在alibaba的《java编程手册》中提到:

4.【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors返回的线程池对象的弊端如下:

1) newFixedThreadPool(int)和newSingleThreadPool():

允许的请求队列长度为Integer.MAX_VALUE,可能会对接大量的请求,从而导致OOM。

2) newCachedThreadPool():

允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

    /**
     * Creates a thread pool that creates new threads as needed, but
     * will reuse previously constructed threads when they are
     * available.  These pools will typically improve the performance
     * of programs that execute many short-lived asynchronous tasks.
     * Calls to {@code execute} will reuse previously constructed
     * threads if available. If no existing thread is available, a new
     * thread will be created and added to the pool. Threads that have
     * not been used for sixty seconds are terminated and removed from
     * the cache. Thus, a pool that remains idle for long enough will
     * not consume any resources. Note that pools with similar
     * properties but different details (for example, timeout parameters)
     * may be created using {@link ThreadPoolExecutor} constructors.
     *
     * @return the newly created thread pool
     */
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

在后续对线程池模型的详细讲解中,可以印证该实践的合理性。

老猿说说-SynchronousQueue_老猿说说的博客-CSDN博客引导语SynchronousQueue 是比较独特的队列,其本身是没有容量大小,比如我放一个数据到队列中,我是不能够立马返回的,我必须等待别人把我放进去的数据消费掉了,才能够返回。SynchronousQueue 在消息队列技术中间件中被大量使用,本文就来从底层实现来看下 SynchronousQueue 到底是如何做到的。1 整体架构SynchronousQueue 的整体设计比较抽象,在内部抽象出了两种算法实现,一种是先入先出的队列,一种是后入先出的堆栈,两种算法被两个内部类实现,而直接对外的https://blog.csdn.net/zlfing/article/details/109802531

https://www.cnblogs.com/aspirant/p/10731108.htmlicon-default.png?t=N7T8https://www.cnblogs.com/aspirant/p/10731108.html

/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

2.4.1 ThreadPoolExecutor

        对创建函数 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)的参数进行一下详细说明:

  • corePoolSize int

  始终保持在线程池中的线程数量,即使这些线程是闲置的。特别说明:如果属性allowCoreThreadTimeOut被设置成了false,那么核心线程释放的时间也以keepAliveTime为准。

//设置allowCoreThreadTimeout
ThreadPoolExecutor leader5ThreadPoolExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 100, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1), new CustomizableThreadFactory("消费线程-"));
leader5ThreadPoolExecutor.allowCoreThreadTimeOut(true);


//源码
public void allowCoreThreadTimeOut(boolean value) {
    if (value && keepAliveTime <= 0)
        throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
    if (value != allowCoreThreadTimeOut) {
        allowCoreThreadTimeOut = value;
        if (value)
            interruptIdleWorkers();
    }
}

  关于核心线程数大小的设置有传说中的大厂经验:

CPU密集型核心线程数=CPU核数+1
IO密集型核心线程数=CPU核数 * 2

  • maximumPoolSize int

  线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。

  • keepAliveTime long

  当线程数量超出核心线程池数量,那么超出的部分在被关闭前的最大闲置时间

  • unit TimeUnit

  keepAliveTime参数的单位

  • workQueue BlockingQueue<Runnable>

  任务在被执行前放置在该队列

  • threadFactory ThreadFactory

  线程池创建线程时使用的创建工厂;线程池有个默认的线程工厂

ThreadFactory threadFactory = Executors.defaultThreadFactory();
  • handler RejectedExecutionHandler

  the handler to use when execution is blocked because the thread bounds and queue capacities are reached.

我们着重解释下ThreadFactory和RejectedExecutionHandler

2.4.2 ThreadFactory

ThreadFactory threadFactory = new ThreadFactory() {
    @Override
    public Thread newThread(@NotNull Runnable r) {
        return null;
        }
    };

2.4.3 RejectedExecutionHandler

  1. 默认是AbortPolicy;该策略直接丢弃提交任务,并抛出RejectedExecutionException异常
  2. DiscardPolicy:该策略也是将任务抛弃掉(对于提交的任务不管不问,什么也不做),不过并不抛出异常。
  3. DiscardOldestPolicy:该策略是当执行器未关闭时,从任务队列workQueue中取出第一个任务,并抛弃这第一个任务,进而有空间存储刚刚提交的任务。使用该策略要特别小心,因为它会直接抛弃之前的任务。
  4. CallerRunsPolicy:该策略并没有抛弃任何的任务,由于线程池中已经没有了多余的线程来分配该任务,该策略是在当前线程(调用者线程)中直接执行该任务。
    public static class Task implements Runnable {
        protected String taskName;
        public Task(String name) {
            super();
            this.taskName = name;
        }
        @Override
        public void run() {
            try {
                System.out.println(this.taskName + " is running.");
                Thread.sleep(500);
            } catch (Exception ignored) {

            }
        }

    }
    public static void main(String[] args) {
        // 创建线程池。线程池的"最大池大小"和"核心池大小"都为1,"线程池"的阻塞队列容量为1。
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1));

        // 设置线程池的拒绝策略为AbortPolicy
        pool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

            }
        });

        try {
            // 新建10个任务,并将它们添加到线程池中
            for (int i = 0; i < 10; i++) {
                Runnable myTask = new Task("Task-" + i);
                pool.submit(myTask);
            }
        } catch (RejectedExecutionException e) {
            e.printStackTrace();
            // 关闭线程池
            pool.shutdown();
        }
    }

2.4.4 ScheduledThreadPoolExecutor

定时器ScheduledExecutorService原理分析_yegeg的博客-CSDN博客ScheduledExecutorService是怎么存放任务,获取即将要执行的任务,它是怎么实现周期性调用的,执行的任务延时这段时间,线程又做了什么,他和线程池有什么关系https://blog.csdn.net/yegeg/article/details/121691961

3 实践整理

3.1到底应该创建几个线程池

3.2核心线程怎么实现一直存活?

阻塞队列方法有四种形式,它们以不同的方式处理操作,如下表。

抛出异常    返回特殊值    一直阻塞    超时退出
插入    add(e)    offer(e)    put(e)    offer(e,time,unit)
移除    remove()    poll()    take()    poll(time,unit)
检查    element()    peek()    不可用    不可用
核心线程在获取任务时,通过阻塞队列的 take() 方法实现的一直阻塞(存活)。
 

4 参考资料

《Java线程池实现原理及其在美团业务中的实践》https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java Executors框架Java中用于管理线程池的一个工具类,它提供了一组便捷的方法来创建和管理线程池,可以帮助程序员更有效地使用线程池来实现多线程编程。 以下是一个简单的Java Executors框架的实现,带有注释: ``` import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorsDemo { public static void main(String[] args) { // 创建一个固定大小的线程池,线程池中的线程数始终为5个 ExecutorService executorService = Executors.newFixedThreadPool(5); // 循环提交任务 for (int i = 0; i < 10; i++) { // 提交任务到线程池 executorService.submit(new Task(i)); } // 关闭线程池 executorService.shutdown(); } // 任务类 static class Task implements Runnable { int taskId; public Task(int taskId) { this.taskId = taskId; } @Override public void run() { System.out.println("Task " + taskId + " is running on Thread " + Thread.currentThread().getName()); } } } ``` 上面的代码创建了一个固定大小为5的线程池,并循环提交10个任务到线程池中。每个任务都是一个简单的Runnable对象,它只是打印一条消息来表示它正在运行。最后,线程池被关闭以释放系统资源。 注释中包含了一些关键的点,例如如何创建线程池、如何提交任务、如何关闭线程池等。在实际开发中,使用Java Executors框架可以帮助程序员更轻松地管理线程池,从而提高程序的并发性和性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值