回顾线程池

线程池构造方法的参数

参数名类型含义
corePoolSizeint核心线程数
maxPoolSizeint最大线程数
keepAliveTinelong保持存活时间
workQueueeBlockingQueue任务存储队列
threadFactoryThreadFactory当线程池需要新的线程的时候,会使用threadFactory来生成新的线程
HandlerRejectedExecutionHandler由于线程池无法接受你所提交的任务的拒绝策略

添加线程规则

  1. 如果线程数小于corePoolSize,创建一个新线程来运行新任务
  2. 如果线程数等于(或大于)corePoolSize但少于maximumPoolSize,则讲任务放入队列
  3. 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程
  4. 如果队列已满,并且线程数大于或等于maxPoolSize,则拒绝

例子:线程池大小为5,最大池大小为10,队列为100。

因为线程中的请求最多会创建5个,然后任务将被添加到队列中,直到达到100。当队列已满时,将创建新的线程maxPoolSize,最多到10个线程,如果再来任务,就拒绝。

增减线程的特点

  1. 通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池。
  2. 线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它。
  3. 通过设置maximumPoolSize为很高的值,可以允许线程池容纳任意数量的并发任务。
  4. 只有在队列填满时才创建多于corePoolSize的线程,如果使用的是无界队列,那么线程数就不会超过corePoolSize。

ThreadFactory 用来创建线程

  • 默认使用Executors.defaultThreadFactory()
  • 创建出来的线程都在同一个线程组
  • 如果自己指定的ThreadFactory,那么就可以改变线程名,线程组,优先级,是否是守护线程等。

工作队列

  • 有三种最常见的队列类型:
  1. 直接交接:SynchronousQueue
  2. 无界队列:LinkedBlockingQueue
  3. 有界的队列:ArrayBlockingQueue

线程池创建(应该手动还是自动)

手动创建更好,因为这样可以更加明确线程池的运行规则,避免资源耗尽的风险

如果是自动创建线程池(即直接调用JDK封装好的构造方法)可能会带来一些问题。
调用JDK封装好的构造方法:

  • newFixedThreadPool
    先来看newFixedThreadPool,先给出一些代码例子
public class FixedThreadPoolTest {
    public static void main(String[] args) {
        final ExecutorService executorService = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Task());
        }
    }
    static class Task implements Runnable{
        @Override
        public void run() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        }
    }
}

运行结果
在这里插入图片描述
从运行结果就可以看出执行1000次任务始终都是1234,这是因为线程是被规定好的,我们来看看源码。

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

第一个参数nThreads原本是corePoolSize,传进来4就变成4,第二个参数是nThreads代表的是maxPoolSize,但和corePoolSize一样,永远不会超过4,第三个参数keepAliveTime实际上是没有意义的,因为不会有线程会回收,第四个参数TimeUnit.MILLISECONDS是时间的一个单位,这里是毫秒,第五个参数LinkedBlockingQueue是一个无界队列,可以容纳任意数量任务。

因此对于这样的线程池,传进去的阻塞队列没有容量上限,所以请求越来越多的时候,容易造成大量内存占用,可能会导致OOM。

下面来演示newFixedThreadPool出错的情况

public class FixedThreadPoolOOM {
    private static ExecutorService executorService = Executors.newFixedThreadPool(1);
    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executorService.execute(new SubThread());
        }
    }
    static class SubThread implements Runnable{
        @Override
        public void run() {
            try {
                Thread.sleep(100000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • SingleThreadExecutor 单线程的线程池:只会唯一的工作线程来执行任务
public class SingleThreadExecutor {
    public static void main(String[] args) {
        final ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new FixedThreadPoolTest.Task());
        }
    }
}

在这里插入图片描述
为什么会这样呢,我们来看看源码

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

核心数量和最大线程数量都是1,也传入了一个无限容量的队列,可以看出和刚才的newFixedThreadPool原理一样。

  • CachedThreadPool 可缓存线程池:具有自动回收多余线程的功能。(默认时间60s)
public class CachedThreadPool {
    public static void main(String[] args) {
        final ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new FixedThreadPoolTest.Task());
        }
    }
}

在这里插入图片描述

有1000个任务创建1000个线程池,过一会没有任务可执行了就会回收线程。但这种线程池有弊端,具体看源码。

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

可以看到核心线程数量为0,而弊端在于第二个参数maximumPoolSize被设置为了Integer.MAX_VALUE,这可能会创建数量非常多的线程,甚至导致OOM

  • ScheduledThreadPool 支持定时及周期性任务执行的线程池

因此手动创建线程池更好。

线程池里的线程数量设定为多少比较合适

  • CPU密集型(加密,计算hash等):最佳线程数为CPU核心数的1-2倍左右。
  • 耗时IO型(读写数据库,文件,网络读写等):最佳线程数一般会大于CPU和学术很多倍
    参考Brain Goets推荐的计算方法:
    线程数=CPU核心数*(1+平均等待时间/平均工作时间)

workStealingPool(JDK1.8)

  • 这个线程池和之前的有很大不同
  • 子任务
  • 窃取

停止线程池的相关方法

  • shutdown 执行后停止
  • isShutdown 判断开始执行停止
  • isTerminated 判断整个程序是否停止
  • awaitTermination 检测一段时间内是否执行完毕
  • shutdownNow 立刻关闭线程池

4种拒绝策略

  • AbortPolicy 直接抛出异常
  • DiscardPolicy 丢弃处理
  • DiscardOldestPolicy 丢弃最老,存在时间最久的
  • CallerRunsPolicy 谁提交的任务谁去执行

钩子方法 (暂停和恢复线程池)

利用钩子函数可以做到暂停和恢复线程池

public class PauseableThreadPool extends ThreadPoolExecutor {
    private boolean isPaused;//并发修改
    private final ReentrantLock lock = new ReentrantLock();//为了线程安全上把锁
    private Condition unpaused = lock.newCondition();//帮助休眠线程

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public PauseableThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        lock.lock();
        try {
            while (isPaused) {
                unpaused.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private void pause() {
        lock.lock();
        try {
            isPaused = true;
        } finally {
            lock.unlock();
        }
    }

    public void resume() {
        lock.lock();
        try {
            isPaused = false;
            unpaused.signalAll();//唤醒
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final PauseableThreadPool pauseableThreadPool = new PauseableThreadPool(10, 20, 10l, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        final Runnable runnable = new Runnable() {

            @Override
            public void run() {
                System.out.println("我被执行");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 10000; i++) {
            pauseableThreadPool.execute(runnable);
        }
        Thread.sleep(1500);
        pauseableThreadPool.pause();
        System.out.println("线程池被暂停了");
        Thread.sleep(1500);
        pauseableThreadPool.resume();
        System.out.println("线程池被恢复了");
    }
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

༄༊心灵骇客༣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值