随笔篇-线程池

线程池

1. 简介

当一个程序中需要多个task需要被并发执行时,最直接的方式是为每一个task创建一个线程去执行,但这样会带来以下问题:

  • 大量线程创建与运行会导致系统不断的启动和关闭新线程,会过渡消耗系统资源.

  • 过度切换线程的危险,从而可能导致系统的崩溃.

  • 同时创建过多的线程意味着要创建过多的Thread对象,这样也会额外增大jvm的垃圾回收压力

在这种情况下,引入"池化技术"是必要的,在实际开发中这种技术得到很多的应用,例如数据库连接池等

池化技术可以简单理解为是一个池子,在这个池子存放着固定的资源,这些资源可以是线程,也可以数据库连接,具体取决于是怎么类型的池

这些资源不会"消失",而是可以被多次复用,从而达到节省资源开销等目的

image-20211102172615693

线程池就是"池化技术"的一种体现,使用该技术可以避免上文中提到的问题,并且好处众多,如下:

  • 不需要创建大量线程,只需要创建一个线程池即可,让线程池去管理线程
  • 加快程序响应速度,合理利用CPU资源
  • ......

2. 参数

创建线程池会需要传入几个参数才能创建成功,如下:

参数名 说明
corePoolSize 核心线程数
maxPoolSize 最大线程数
keepAliveTime 保持存活时间
workQueue 任务存储队列
threadFactory 线程工厂
rejectHandler 拒绝处理器

关于线程池中线程创建过程大体流程如下(含参数解释):

  • 当线程池创建并初始化完成,此时线程池里面并没有任何资源,当有任务过来需要被执行时才会去创建核心线程执行任务

  • 线程不会无限制创建,当创建线程数超过corePoolSize时,就会把任务存储在workQueue

    如果创建的线程数没有超过corePoolSize时,即使线程池有线程时空闲的,也还是会创建线程

  • workQueue中存储任务已满,则再会去创建线程从队列中拉取任务执行,线程也不会无限制创建,当创建的线程达到maxPoolSize时,则不会创建了

  • 当线程达到maxPoolSize,且workQueue存满时,则会根据rejectHandler执行拒绝策略

  • 最后多余非核心线程的空闲时间超过配置的keepAliveTime,那么线程进行停止销毁

    核心线程会一直存在,不会销毁

具体流程图如下:

image-20211102180822666

ThreadFactory

线程工厂,用来创建线程的,在创建线程池时,可以使用开发人员定义的线程工厂,也可以使用默认提供的ThreadFactory

默认的ThreadFactory使用Executors.defaultThreadFactory()创建,该线程工程创建出来的线程都是在同一个线程组,且都不是守护线程

如果开发人员想要自己指定创建出来的线程名,线程组,优先级,是否为守护线程则可以使用自己的线程工厂


WorkQueue

工作队列,或者叫任务队列,用来存储任务,当核心线程已满,且核心线程都忙碌时,则将任务存储到工作队列中,直接工作队列也存储满,才会去创建非核心线程,如上文图

工作队列为阻塞队列,常用一般分为三个,可以由开发人员自由选择

  • SynchronousQueue

    该队列实现了BlockingQueue,类图如下:

    image-20211103102056762

    该队列可以理解为是一个直接交接队列,或者为中转队列,当有任务过来,该队列就会立马交给线程池的线程执行

    该队列是没有容量的,因此如果线程池采用这种可以将此案成maxPoolSize设置大一些

    这样只要有任务过来,该队列就会交给线程池执行

  • LinkedBlockingQueue

    该队列可以理解为"无边界队列",同样的也实现了BlockingQueue,如下:

    image-20211103103420548

    当创建该队列时,如果没有指定大小,那么该队列则无上限

    如果采用该队列,意味着maxPoolSize参数无作用,因为队列有可能永远存储不满

    注意:其实也不是存储不满,存储的上限时Integer.MAX_VALUE

  • ArrayBlockingQueue

    该队列可以理解为"有边界队列",同样的也实现了BlockingQueue,如下:

    image-20211103103549737

    这种队列创建就需要指定大小,这样也就意味着maxPoolSize是有意义的了

    具体选择何种队列,则需要根据具体场景来进行选择

3. 创建

线程池创建分为两大类:

  • 手动创建

    手动创建,即自己创建线程池对象ThreadPoolExecutor,这种方式可以更加的去理解线程池规则,规避风险

    在阿里巴巴开发手册中,也提到过,系统开发过程中,不允许使用jdk默认提供的线程池,如图:

    image-20211103104631291
  • 自动创建

    自动创建即使用jdk默认提供的线程池,虽然阿里巴巴开发手册上不允许使用,但是还是需要了解一下

3.1 自动

jdk默认封装的线程池主要有以下:

  • FixedThreadPool
  • SingleThreadPool
  • CachedThreadPool
  • ScheduledThreadPool

接下来就从其说明,使用,缺点问题方面探讨这些线程池之间的优缺点


3.1.1 FixedThreadPool

  • 说明

    这种线程池创建时,只需要传入一个核心线程数即可,如下:

     ExecutorService executor = Executors.newFixedThreadPool(2);
    

    从源码其源码可知,该线程池核心线程数与最大线程数一样,采用LinkedBlockingQueue,如下:

    image-20211103112142577

    从源码可知,其最大线程数并没有任何意义,其工作队列无上限,意味着任务的存储无上限(其实也不是无上限,最大上限为Integer.maxValue)

  • 使用

    使用该类型线程池处理线程,如下:

    package com.tomato.thread.pool;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class FixThreadPool {
        public static void main(String[] args) {
            // 创建线程池,并指定核心线程数为2
            ExecutorService executorService = Executors.newFixedThreadPool(2);
    
            for (int i = 0; i < 10; i++) {
                // 循环往线程池中提交任务,循环十次,即意味着提交了十个任务
                // 由于核心线程只有2个,意味着最多只有2个线程在执行任务
                // 没有执行的任务就在等待
               // 任务都是Runable类型对象,所以这里为了简便用了lamda
                executorService.execute(() -> {
                    System.out.println(
                    Thread.currentThread().getName());
                });
    
            }
        }
    
    }
    

    运行结果如下:

    image-20211103113109106

    从图中可以看出,最多两个线程在执行任务

  • 缺点

    由于任务存储队列没有上限,假如执行的任务耗时较久,在任务较多的情况下,就意味着存储队列中会不停的存储任务,这样会导致最后oom,如图:

    image-20211109102829059

    代码演示如下:

    package com.tomato.thread.pool;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class FixThreadPoolOOM {
        public static void main(String[] args) {
            // 为了更好的测试,这里将核心线程数设置为1
            ExecutorService executorService = Executors.newFixedThreadPool(1);
            for(int i = 1; i < 100_000; i++) {
                // 往线程池中添加10W个任务
                executorService.execute(() -> {
                    try {
                        // 这里休眠50s是为了模拟耗时操作
                        // 当核心在执行该任务时,其他任务没有线程去被执行就只能先存储在队列中
                        // 如果队列中元素过多肯定会报oom
                        Thread.sleep(50_000);
                        System.out.println(
                            Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        }
    }
    
    

    为了更加明显突出,直接修改-Xmx 6m -Xms 6m,调小jvm运行内存,如下图:

    image-20211103115319924

    结果如下:

    image-20211103121610336

3.1.2 SingleThreadPool

该线程池从名字可以看出是单个线程,因此在创建线程池的时候也不需要传入核心线程数,如下:

 ExecutorService executor = Executors.newSingleThreadExecutor();

从其创建的源码可知,该线程池使用的也是"无边界阻塞队列",如下:

image-20211109094219578

因此如果在当个任务处理的情况下,也会发生OOM,原理同FixThreadPool原理一样,这里就不再演示

3.1.3 CachedThreadPool

  • 说明

    该线程池与之前线程池创建一样,不需要传入核心线程数,如下:

     ExecutorService executor = Executors.newCachedThreadPool();
    

    但是通过源码可以得知,该线程池的核心线程为0,而非核心线程数为Integer.MAX_VALUE,使用的也是中转队列,如下:

    image-20211109101404414
  • 使用

    使用该线程池处理任务代码如下:

    package com.tomato.thread.pool;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    import static java.text.MessageFormat.format;
    
    public class CacheThreadPoool {
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newCachedThreadPool();
    
            for(int i = 0; i < 1_000; i++) {
                executorService.execute(() -> {
                    System.out.println(format("{0}执行任务",Thread.currentThread().getName()));
                });
            }
        }
    }
    

    运行结果如下:

    image-20211109101959313
  • 缺点

    从其源码可知,当有任务过来会立马交给线程池的线程执行,而其核心线程为0,最大线程为Integer.MAX_VALUE

    如果当执行的任务是一个耗时任务,在任务较多的情况下,就会频繁的创建线程对象,从而有可能发生OOM,如下:

    image-20211109103029903

    代码演示如下:

    package com.tomato.thread.pool;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    import static java.text.MessageFormat.format;
    
    
    public class CacheThreadPoolOOM {
        public static void main(String[] args) {
            ExecutorService executor = Executors.newCachedThreadPool();
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                executor.execute(() -> {
                    try {
                        Thread.sleep(50_000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(format("{0}执行任务完成", Thread.currentThread().getName()));
                });
            }
        }
    }
    
    

    同时为了更快看到效果,这里设置JVM参数-Xms2m -Xmx2m,如下:

    image-20211109103845255

    因此在阿里开发规范中,该类型的线程池与上述类型的两种线程池是不允许使用的

3.1.4 ScheduledThreadPool

该类型的线程池,是一个具备周期性执行的一个线程池,在这里不再解释,之前的章节有过详细描述

3.2 手动

  • 说明

    在上述中主要是利用jdk提供的线程池去进行创建,但是也提到了每个线程池的局限性,因此在实际开发中手动创建线程池机会反而多点

  • 代码

    因此在具体创建时,代码如下:

    package com.tomato.thread.pool;
    
    import java.text.MessageFormat;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    import static java.text.MessageFormat.*;
    
    /**
     * 自定义线程池
     */
    public class CustomThreadPool {
        public static void main(String[] args) {
            ThreadPoolExecutor executor =
                    new ThreadPoolExecutor(
                            2,
                            10, 
                            0,
                            TimeUnit.SECONDS,
                            new LinkedBlockingDeque<>(20));
            for(int i = 0; i < 30; i++) {
                executor.execute(() -> {
                    System.out.println(format("{0}执行任务",Thread.currentThread().getName()));
                });
            }
        }
    }
    
    

    执行结果如下:

    image-20211109112033050

    这样做的好处可以自己控制核心线程数,最大线程数,以及存储队列等

4. 核心数

当手动创建线程池时,如何去确定核心线程数为多少,目前在一些实践中主要分为以下两类:

  • CPU密集型(经常加密,计算hash等)

    线程核心数此时应该为 CPU可用核数的1~2倍

  • IO密集型(数据库读写,文件读写,网络读写)

    一般该线程数为核心线程数的很多倍,根据 Brain Goetz的公式为:

    CPU 核心数 * (1 + 平均等待时间/平均工作时间)

5. Factory

在上述中,不管是手动创建线程池还是自动创建线程池,使用的ThreadFactory都是默认的线程工厂

有时候在创建线程池时,想要修改线程池一些参数,例如线程名字等,这样就可以使用自定义线程工厂,如下:

package com.tomato.thread.pool;

import java.text.MessageFormat;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import static java.text.MessageFormat.*;

public class CustomThreadFactory implements ThreadFactory {
    private ThreadGroup threadGroup;

    private AtomicLong threadNumber = new AtomicLong(1L);

    /**
     * 线程池中线程组名字前缀
     */
    private String prefix;

    public CustomThreadFactory() {
        this("");
    }

    public CustomThreadFactory(String prefixName) {
        SecurityManager securityManager = System.getSecurityManager();
        threadGroup = securityManager != null ? securityManager.getThreadGroup() : Thread.currentThread().getThreadGroup();
        prefix = (prefixName != null && !"".equals(prefixName)) ? prefixName : "tomato-pool-thread";
    }

    @Override
    public Thread newThread(Runnable r) {
        /**
         * 当设置stackSize属于<=0 时,以-Xss为准
         * 当设置stackSize属于(0, 4k]区间时,设置的-Xss会失效,栈空间取默认值1M
         * 当设置stackSize属于(4k, 64k]区间时,设置的-Xss会失效,栈空间取4k。
         * 当设置stackSize属于(64k, 128k]区间时,设置的-Xss会失效,栈空间取64k。
         * 当设置stackSize属于 >128k 时,设置的-Xss会失效,栈空间取stackSize本身
         */
        Thread thread = new Thread(threadGroup,r, prefix + "-" + threadNumber.getAndIncrement(), 0);

        if (thread.isDaemon()) {
            thread.setDaemon(false);
        }

        if (thread.getPriority() != Thread.NORM_PRIORITY) {
            thread.setPriority(Thread.NORM_PRIORITY);
        }

        return thread;
    }
}

class Test {
    public static final Integer ACPU = Runtime.getRuntime().availableProcessors();
    public static void main(String[] args) {
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(
                        ACPU * 2 + 1,
                        ACPU * 2 + 1,
                        0,
                        TimeUnit.SECONDS,
                        new LinkedBlockingDeque<>(600),
                        new CustomThreadFactory());

        for(int i = 1; i < 1_00; i++) {
            executor.execute(() -> {
                System.out.println(format("{0}执行任务完成",Thread.currentThread().getName()));
            });
        }
    }
}

执行结果,发现线程的名字都改变,如下:

image-20211109134421034

这样就完成了自定义ThreadFactory的实现

6. Reject

从上文的描述中可知,线程池的流程是当任务存储队列满了的时候,则会创建非核心线程去执行队列中的任务

那如果所有的线程都处于忙碌中,且队列中存储满了,那么当任务再过来就会执行配置的

拒绝策略去拒绝任务,在线程池中拒绝策略主要有三个,如下:

如果线程池异常关闭,有任务过来也会异常拒绝

  • AbortPolicy

    终止策略,当任务被拒绝时,则抛出RejectExecutionException异常

    这种策略也是默认的策略

  • CallerRunsPolicy

    调用者策略,如果线程池使用该策略拒绝了该任务,那么该任务由哪个线程提交的就由哪个线程执行

  • DiscardOldestPolicy

    丢弃最早未处理请求策略,当使用该策略时,线程池会丢弃最先进入阻塞队列中的任务,给最新的任务腾出空间

  • DiscardPolicy

    丢弃策略,使用该策略,就会丢弃最新任务

  • 自定义

    当然也可以根据其策略去自定义拒绝策略

关于四种策略,其详细解释如下文

6.1 AbortPolicy

  • 说明

    该策略为终止策略,也是线程默认的策略,其原理就是当队列存储已满,且无任何空闲线程时,就会抛出RejectExcutionException,原理图如下:

    image-20211109141419872

    同时从源码可以看出该策略,其方式就是抛出异常,如下:

    image-20211109141752480
  • 使用

    创建线程池使用该策略,如下:

    package com.tomato.thread.pool;
    
    import java.text.MessageFormat;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 策略方式为抛异常
     */
    public class AbortPolicyTest {
        public static void main(String[] args) {
            final Integer ACPU = Runtime.getRuntime().availableProcessors();
            ThreadPoolExecutor executor =
                    new ThreadPoolExecutor(
                            2 * ACPU + 1,
                            2 * ACPU + 1,
                            0,
                            TimeUnit.SECONDS,
                            new LinkedBlockingDeque<>(6),
                            new ThreadPoolExecutor.AbortPolicy());
            for (int i = 0; i <= 100; i++) {
                executor.execute(() -> {
                    try {
                        // 模拟耗时
                        Thread.sleep(100);
                        System.out.println(MessageFormat.format("{0}执行完成", Thread.currentThread().getName()));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        }
    }
    
    

    从代码可以看出,阻塞队列最大容量是6,而添加的任务是100,因此当超过容量,且线程都处于忙碌时,则会抛出异常,如下:

    image-20211109143945153

6.2 CallerRunsPolicy

  • 说明

    如果创建线程池使用该策略拒绝了该任务,那么该任务由哪个线程提交的就由哪个线程执行,如图:

    image-20211109144806090

    从源码也可以看出,该方法直接被执行也,也就是哪个线程提交哪个执行,而不是让线程池的线程去执行,如图:

    image-20211109145120665
  • 使用

    创建线程池使用该策略,如下:

    package com.tomato.thread.pool;
    
    import java.text.MessageFormat;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    public class CallerRunPolicyTest {
        public static void main(String[] args) {
            final Integer ACPU = Runtime.getRuntime().availableProcessors();
            ThreadPoolExecutor executor =
                    new ThreadPoolExecutor(
                            2 * ACPU + 1,
                            2 * ACPU + 1,
                            0,
                            TimeUnit.SECONDS,
                            new LinkedBlockingDeque<>(6),
                            new ThreadPoolExecutor.CallerRunsPolicy());
            for (int i = 0; i <= 100; i++) {
                executor.execute(() -> {
                    try {
                        // 模拟耗时
                        Thread.sleep(100);
                        System.out.println(MessageFormat.format("{0}执行完成", Thread.currentThread().getName()));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
    
        }
    }
    

    最后从结果也可以得知,当线程池塞满时,也会有一些任务由主线程执行,如下:

    image-20211109150326337

6.3 DiscardOldestPolicy

  • 说明

    当使用该策略时,线程池会丢弃最先进入阻塞队列中的任务,给最新的任务腾出空间,如图:

    image-20211109151124441

    当然从其源码也可以知道,先从队列弹出一个task,再把最新的任务添加到线程池,如下

    image-20211109151448261
  • 使用

    创建线程池,使用该策略如下:

    package com.tomato.thread.pool;
    
    
    import java.text.MessageFormat;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class DiscardOldestPolicyTest {
        private final static AtomicInteger atomic = new AtomicInteger(1);
    
        public static void main(String[] args) {
            final Integer ACPU = Runtime.getRuntime().availableProcessors();
            ThreadPoolExecutor executor =
                    new ThreadPoolExecutor(
                            2 * ACPU + 1,
                            2 * ACPU + 1,
                            0,
                            TimeUnit.SECONDS,
                            new LinkedBlockingDeque<>(6),
                            new ThreadPoolExecutor.DiscardOldestPolicy());
            for (int i = 0; i <= 100; i++) {
                executor.execute(() -> {
                    try {
                        // 模拟耗时
                        Thread.sleep(100);
                        System.out.println(MessageFormat.format("{0}:执行{1}个任务完成", Thread.currentThread().getName(), atomic.getAndIncrement()));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        }
    }
    
    

    从结果也可以看出,有些任务并没有执行而是直接被舍弃了,如下:

    pool-1-thread-18:执行3个任务完成
    pool-1-thread-19:执行4个任务完成
    pool-1-thread-21:执行1个任务完成
    pool-1-thread-1:执行15个任务完成
    pool-1-thread-17:执行6个任务完成
    pool-1-thread-16:执行2个任务完成
    pool-1-thread-24:执行25个任务完成
    pool-1-thread-22:执行23个任务完成
    pool-1-thread-13:执行8个任务完成
    pool-1-thread-14:执行7个任务完成
    pool-1-thread-7:执行18个任务完成
    pool-1-thread-25:执行19个任务完成
    pool-1-thread-23:执行9个任务完成
    pool-1-thread-5:执行22个任务完成
    pool-1-thread-8:执行12个任务完成
    pool-1-thread-20:执行5个任务完成
    pool-1-thread-6:执行24个任务完成
    pool-1-thread-12:执行21个任务完成
    pool-1-thread-3:执行20个任务完成
    pool-1-thread-9:执行10个任务完成
    pool-1-thread-10:执行13个任务完成
    pool-1-thread-11:执行17个任务完成
    pool-1-thread-2:执行14个任务完成
    pool-1-thread-4:执行16个任务完成
    pool-1-thread-15:执行11个任务完成
    pool-1-thread-17:执行31个任务完成
    pool-1-thread-16:执行26个任务完成
    pool-1-thread-18:执行30个任务完成
    pool-1-thread-1:执行28个任务完成
    pool-1-thread-21:执行29个任务完成
    pool-1-thread-19:执行27个任务完成
    

6.4 DiscardPolicy

  • 说明

    使用该策略,如果线程池想要拒绝任务,则会将最新的任务丢弃,原理如下:

    image-20211109152300372

    当然从代码也看出,丢弃了最新任务:

    image-20211109152436923
  • 使用

    package com.tomato.thread.pool;
    
    
    import java.text.MessageFormat;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class DiscardPolicy {
    
        private final static AtomicInteger atomic = new AtomicInteger(1);
    
    
        public static void main(String[] args) {
            final Integer ACPU = Runtime.getRuntime().availableProcessors();
            ThreadPoolExecutor executor =
                    new ThreadPoolExecutor(
                            2 * ACPU + 1,
                            2 * ACPU + 1,
                            0,
                            TimeUnit.SECONDS,
                            new LinkedBlockingDeque<>(6),
                            new ThreadPoolExecutor.DiscardPolicy());
            for (int i = 0; i <= 100; i++) {
                executor.execute(() -> {
                    try {
                        // 模拟耗时
                        Thread.sleep(100);
                        System.out.println(MessageFormat.format("{0}:执行{1}个任务完成", Thread.currentThread().getName(), atomic.getAndIncrement()));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        }
    }
    

    从结果看出,有些任务并没有执行,而是直接被舍弃了,如下:

    pool-1-thread-21:执行25个任务完成
    pool-1-thread-11:执行21个任务完成
    pool-1-thread-10:执行20个任务完成
    pool-1-thread-7:执行4个任务完成
    pool-1-thread-16:执行8个任务完成
    pool-1-thread-4:执行13个任务完成
    pool-1-thread-18:执行22个任务完成
    pool-1-thread-25:执行7个任务完成
    pool-1-thread-12:执行14个任务完成
    pool-1-thread-6:执行17个任务完成
    pool-1-thread-2:执行24个任务完成
    pool-1-thread-19:执行18个任务完成
    pool-1-thread-13:执行11个任务完成
    pool-1-thread-5:执行16个任务完成
    pool-1-thread-1:执行3个任务完成
    pool-1-thread-20:执行10个任务完成
    pool-1-thread-8:执行5个任务完成
    pool-1-thread-23:执行12个任务完成
    pool-1-thread-24:执行23个任务完成
    pool-1-thread-3:执行15个任务完成
    pool-1-thread-17:执行9个任务完成
    pool-1-thread-22:执行6个任务完成
    pool-1-thread-15:执行1个任务完成
    pool-1-thread-9:执行19个任务完成
    pool-1-thread-14:执行2个任务完成
    pool-1-thread-10:执行29个任务完成
    pool-1-thread-7:执行30个任务完成
    pool-1-thread-4:执行31个任务完成
    pool-1-thread-21:执行26个任务完成
    pool-1-thread-16:执行28个任务完成
    pool-1-thread-11:执行27个任务完成
    

6.5 sumary

四种策略,具体采用哪种策略还是需要根据具体的业务场景来实现

代码地址: https://gitee.com/wangzh991122/thread-juc

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

推荐阅读更多精彩内容