线程池总结加实战

使用线程池的好处

  1. 降低资源消耗。
  2. 提高响应速度。
  3. 提高线程的可管理性。

线程池的实现原理


当用户提交了一个任务到线程池,线程池执行流程如下:

  1. 线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下一个流程。
  2. 线程池判断工作队列是否已满。如果没满,则将新提交的任务存储在这个工作队列中。如果满了则进入下一个流程。
  3. 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果满了,则交给饱和策略来处理。

参数解析


常见的构造函数如下:

public ThreadPoolExecutor(
    int corePoolSize,int maximumPoolSize, long keepAliveTime, 
    TimeUnit unit,BlockingQueue<Runnable> workQueue,
    ThreadFactory threadFactory,RejectedExecutionHandler handler)
  1. corePoolSize。核心线程的数量。即使这些线程是空闲状态也不会被销毁。如果提前调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有的基本线程。
  2. maximumPoolSize。线程允许的最大线程数。
  3. keepAliveTime。如果一个线程处于空闲状态,并且当前线程数量大于 corePoolSize,在 keepAliveTime 时间过后这个空闲线程将会被销毁。
  4. unit。keepAliveTime 的单位。
  5. workQueue。工作队列。当新的任务被提交后,会进入该工作队列。
  6. threadFactory。创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等。
  7. handler。拒绝策略。

任务队列

  1. ArrayBlockingQueue。基于数组的有界阻塞队列,先进先出(FIFO)。
  2. LinkedBlockingQueue。基于链表的阻塞队列,先进先出(FIFO),吞吐量高于 ArrayBlockingQueue
  3. SynchronousQueue。一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则一直处于阻塞状态。
  4. PriorityBlockingQueue。具有优先级的无限阻塞队列。

拒绝策略

有如下几种拒绝策略

  1. AbortPolicy。直接抛异常。
  2. CallerRunsPolicy。只有调用者所在线程来运行任务。
  3. DiscardOldestPolicy。丢弃队列里最近的一个任务(最先进入队列的,最老的),并执行当前任务。
  4. DiscardPolicy。不处理,丢弃掉。


依次验证。

class DemoThread extends Thread {
    int code;

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " running,code " + code);
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void setCode(int code) {
        this.code = code;
    }
}


定义一个线程类,给一个编号 code,在线程运行过程中打印当前线程的名字和编码,然后睡眠3秒。

public static void test(RejectedExecutionHandler rejectedExecutionHandler) {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS,
                                                                   new ArrayBlockingQueue<>(10), new DefaultThreadFactory("demo"), rejectedExecutionHandler);
    for (int i = 0; i < 100; i++) {
        DemoThread thread = new DemoThread();
        thread.setCode(i);
        threadPoolExecutor.execute(thread);
    }
}

定义一个测试方法,在该方法内定义一个线程池,核心线程数是2,最大线程数是5,任务队列最大值是10,拒绝策略由调用方传入。如:

public static void main(String[] args) {
    test(new ThreadPoolExecutor.AbortPolicy());
}

接下来开始测试。

AbortPolicy

测试结果:

demo-1-1 running,code 0
demo-1-2 running,code 1
demo-1-3 running,code 12
demo-1-4 running,code 13
demo-1-5 running,code 14
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Thread[Thread-15,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@1e80bfe8[Running, pool size = 5, active threads = 5, queued tasks = 10, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at thread.ThreadPoolDemo.test(ThreadPoolDemo.java:25)
	at thread.ThreadPoolDemo.main(ThreadPoolDemo.java:16)
demo-1-1 running,code 2
demo-1-4 running,code 5
demo-1-3 running,code 6
demo-1-2 running,code 3
demo-1-5 running,code 4
demo-1-1 running,code 7
demo-1-5 running,code 8
demo-1-4 running,code 9
demo-1-2 running,code 10
demo-1-3 running,code 11

分析:0号任务和1号任务先被核心线程处理,由于核心线程已满,所以2到11号任务被放到阻塞队列中,当阻塞队列满了之后,将会开辟新的线程来处理12、13和14号任务,当15号任务进来之后,由于核心线程池、阻塞队列都满来,并且也达到来最大线程数,此时会执行 AbortPolicy 拒绝策略,直接抛出异常,此时主线程不会再产生任务了。后面线程池将会从任务队列中拿出任务来接着执行。

CallerRunsPolicy

测试结果:

demo-1-1 running,code 0
demo-1-2 running,code 1
demo-1-3 running,code 12
demo-1-4 running,code 13
main running,code 15
demo-1-5 running,code 14
main running,code 16
demo-1-5 running,code 2
demo-1-4 running,code 3
demo-1-2 running,code 5
demo-1-3 running,code 4
demo-1-1 running,code 6
demo-1-5 running,code 7
demo-1-3 running,code 11
main running,code 27
demo-1-2 running,code 10
demo-1-1 running,code 8
demo-1-4 running,code 9
demo-1-5 running,code 17
demo-1-1 running,code 20
demo-1-2 running,code 19
demo-1-3 running,code 18
main running,code 30
…………

分析:分析过程跟之前一样,可以看到15号任务是被 main 线程调用的。值得注意的是,在 mian 线程执行15号任务的时候,后面的任务并没有被生产出来,这种方式可以减缓线程池本身的压力,但是会增加 main 线程的压力。

DiscardOldestPolicy

测试结果:

demo-1-1 running,code 0
demo-1-2 running,code 1
demo-1-3 running,code 12
demo-1-4 running,code 13
demo-1-5 running,code 14
demo-1-4 running,code 90
demo-1-1 running,code 91
demo-1-2 running,code 92
demo-1-3 running,code 94
demo-1-5 running,code 93
demo-1-1 running,code 96
demo-1-4 running,code 95
demo-1-2 running,code 97
demo-1-3 running,code 98
demo-1-5 running,code 99

分析:0号和1号任务正常进入核心线程池,2到11号进入阻塞队列,12、13和14号被普通线程处理,后面直接执行的是第90号任务,说明第2到89号任务都被抛弃了。

DiscardPolicy

测试结果:

demo-1-1 running,code 0
demo-1-2 running,code 1
demo-1-3 running,code 12
demo-1-4 running,code 13
demo-1-5 running,code 14
demo-1-4 running,code 2
demo-1-5 running,code 3
demo-1-1 running,code 6
demo-1-2 running,code 4
demo-1-3 running,code 5
demo-1-2 running,code 7
demo-1-4 running,code 10
demo-1-3 running,code 9
demo-1-5 running,code 8
demo-1-1 running,code 11

分析:这个结果跟上一个结果有点相反的味道,因为从第14号任务之后,所有的任务都被抛弃掉了,但是并没有影响先进入队列里的任务。

参数配置


CPU 密集型任务应配置尽可能小的线程,如配置 N+1 个线程的线程池。
IO 密集型任务线程尽可能多配置线程数量,如 2N,因为这种类型的任务并不是一直在执行任务。
有这样一个公式,核心数 N,task 本地执行时间是 X,等待时间是 Y,那么核心线程可以设置为 N*(X+Y)/X。
可通过Runtime.getRuntime().availableProcessors()获取当前设备的 CPU 个数。

Executors 创建线程池

newFixedThreadPool


创建一个固定大小的线程池,核心线程数和队列数都是一样固定的。

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,0L,
        TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
    }

newCachedThreadPool


创建一个具有缓存功能的线程池,核心线程数是 0,但是队列的数量是 Integer.MAX_VALUE,线程超过60s空闲将会销毁。它比较适合处理执行时间比较小的任务。

public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L,
    TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),threadFactory);
}

SingleThreadExecutor


它只会创建一条工作线程处理任务。

public static ExecutorService newSingleThreadExecutor(){
    return new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>());
}

ScheduledThreadPool


创建一个调度任务的线程池。

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1));
}

线程池的停止


停止线程池有两种 api。

  1. void shutdown()
  2. List<Runnable> shutdownNow()


第一种方式会停止接收新的任务,但是会等已经存在的任务完成才真正的停止。
第二种方式会停止执行正在执行的任务,并返回未执行的任务。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值