Java线程池一原理参数解释

线程池

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互
而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时
更应该考虑使用线程池

线程池里的每个线程代码结束后并不会死亡
而是再次回到线程池中成为空闲状态,等待下一个对象再来使用

JDK5之前,要手动实现线程池,从JDK 5开始,Java内置支持线程池

Executor和ExecutorService

Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池
而只是一个执行线程的工具


比较重要的几个类
public interface Executor {
    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

真正的线程池接口是ExecutorService,定义了各种方法

public interface ExecutorService extends Executor {

    void shutdown();

    List<Runnable> shutdownNow();

    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的子类

ThreadPoolExecutor的构造方法

构造线程池的基本参数

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  • corePoolSize:核心线程数,指保留的线程池大小(不超过maximumPoolSize值时,线程池中最多有corePoolSize 个线程工作)
  • maximumPoolSize:指的是线程池的最大大小(线程池中最大有maximumPoolSize 个线程可运行)
  • keepAliveTime :线程数大于核心时,空闲线程结束的超时时间(当一个线程不工作时,超过keepAliveTime 指定时间将停止该线程)
  • unit:是一个枚举,表示 keepAliveTime 的单位(有NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS,7个可选值)
  • workQueue:表示存放任务的队列(存放需要被线程池执行的线程队列)
  • threadFactory:线程工厂
  • handler:拒绝策略(添加任务失败后如何处理该任务)

corePoolSize、maximumPoolSize、BlockingQueue之间的关系

所有的BlockingQueue都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:

  • 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
  • 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
  • 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

corePoolSize一般不会设置的很大,会根据CPU核心数和需求场景来设置
其实线程池的配置主要在这三个参数了,他们之间是有相互关系的

看Demo:

class ThreadRunnable implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);//不加延时 可能出不来效果
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":正在执行");
    }
}

创建线程池并添加任务

       ExecutorService  pool = new ThreadPoolExecutor(
                4,
                10,
                60,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(128)
        );

        for (int i = 0; i < 30; i++) {
            pool.execute(new ThreadRunnable());
        }

结果:


image.png

如果将 new LinkedBlockingDeque<>(128)的128改为5
再看结果:


image.png

错误详情:

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task ThreadRunnable@7ea987ac rejected from java.util.concurrent.ThreadPoolExecutor@12a3a380[Running, pool size = 10, active threads = 10, queued tasks = 5, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
    at ThreadPoolDemo.main(ThreadPoolDemo.java:22)

那为什么会出现这样的情况呢?
首先看下线程池是如何工作的:
首先任务加入队列,创建线程,从任务队列获取任务并执行

  • 如果任务队列只有一个任务的话,其实只创建一个线程就够了
  • 如果任务大于数大于1,会创建多个线程,最多创建核心线程数个线程
  • 如果任务数大于最大线程数,其他任务会加入到队列,等待核心线程数的任务执行完,再从队列获取任务执行,直到队列中没有任务为止,线程处于空闲状态,超过keepAliveTime,将会被销毁
  • 如果任务数超过队列的限界后,将会继续创建线程数目到最大线程数,会执行拒绝策略

Demo设置的核心线程数4,最大线程数10,任务总数为20

  • 在队列为128的时候,会将任务加入队列,等待核心线程的任务执行完,再去队列获取新的任务
    如果没有任务了
  • 在队列长度改为5的时候,任务来不及执行,且队列长度不够,最大线程数+队列长度=15 < 20
    队列不够存放了,就会报错

源码:

   public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        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);
    }

线程池排队策略

排队有三种通用策略:

    1. 直接提交。工作队列的默认选项是 [SynchronousQueue],
      它将任务直接提交给线程而不保持它们。
      在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,
      因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。
      直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。
      当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
    1. 无界队列。使用无界队列(例如,不具有预定义容量的 [LinkedBlockingQueue]
      将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。
      这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)
      当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;
      例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,
      当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
    1. 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 [ArrayBlockingQueue])
      有助于防止资源耗尽,但是可能较难调整和控制。
      队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、
      操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。
      如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。
      使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,
      这样也会降低吞吐量。
  • 4.PriorityBlockingQueue是一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序升序排序(ArrayBlockingQueue和LinkedBlockingQueue 都是采用FIFO原则来确定线程执行的先后顺序),
    当然也可以通过构造函数来指定Comparator来对元素进行排序。
    需要注意的是PriorityBlockingQueue不能保证同优先级元素的顺序。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容