[读书笔记][java并发编程实践] 第8章-线程池的使用

1. 任务的隐性耦合

  1. 线程池中的任务互相依赖,需要小心维护,避免活跃性问题
  2. 线程封闭机制的任务
  3. 对响应时间敏感的任务
  4. 使用ThreadLocal的任务

线程池中的任务需要无限期等待一些必须由池中其他任务才能提供的资源或条件,除非线程池足够大,否则会发生线程饥饿死锁

2. 线程池配置

2.1 线程池大小

线程池过大,大量线程在较少的CPU和内存上竞争资源,导致过高的内存使用量,可能耗尽资源;线程池过小,导致空闲的处理器无法执行工作,降低吞吐量

对于计算密集型任务,线程池大小应该设置为Ncpu + 1

对于I/O密集型任务,需要考虑平均等待I/O时间和实际计算消耗时间的比例,具体遵循如下公式

N_thread = N_cpu * U_cpu * (1 + W / C)

N_cpu = Runtime.getRuntime().availableProcessors();

W是平均等待时间,C是平均计算时间

2.2 线程池配置

2.2.1 线程池基本配置

Executors构造方法

包括Executors各个工厂方法返回的线程池,基本上都是ThreadPoolExecutor类,其构造方法提供了配置的方式

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
   ......
}
  1. corePoolSize:决定了线程池的基础大小,当新任务提交时,如果当前运行的线程数小于corePoolSize,则不管当前这些线程是否空闲,都会新创建一个线程来处理任务
  2. maximumPoolSize:决定了线程池的最大大小,当新任务提交时,如果当前运行的线程数大于corePoolSize,并且小于maximumPoolSize,那么仅当队列满时,才会新建线程,否则任务只会被入队,等待现有线程取出并处理
  3. keepAliveTime,如果线程的空闲时间超过了存活时间,则会标记为可回收的,当线程池大小超过了基本大小,则该线程会被终止
  4. workQueue,可以由用户代码提供工作队列,也可以使用默认实现
  5. threadFactory,线程工厂,线程池创建线程的时候调用该工厂的newThread方法,用于实现个性化线程管理,也可以使用默认实现
  6. RejectedExecutionHandler,饱和策略,当有界队列被填满时的策略,也可以使用默认实现

Executors工厂类方法

工厂方法corePoolSizemaximumPoolSizekeepAliveTimeworkQueue
newFixedThreadPool参数指定参数指定0L(不会因为空闲时间而回收线程)LinkedBlockingQueue(无界队列,可无限入队,直到内存溢出)
newCachedThreadPool0Integer.MAX_VALUE60sSynchronousQueue(无缓冲队列,不负责任务入队,直接移交给消费者)
newSingleThreadExecutor110L(不会因为空闲时间而回收线程)LinkedBlockingQueue(无界队列,可无限入队,直到内存溢出)

2.2.2 管理队列任务

ThreadPoolExecutor允许传递BlockingQueue来保存等待的任务,排队方式有3种:

  1. 无界队列,newFixedThreadPool和newSingleThreadPool使用无界的LinkedBlockingQueue来实现,如果任务到达速度超过处理速度,则会无限制的增加,造成资源耗尽
  2. 有界队列,使用ArrayBlockingQueue等实现有界队列,当队列被填满,则使用饱和策略来处理;如果线程池较小,队列较大,有助于减少内存使用量,降低CPU使用率,减少上下文切换,付出的代价是限制了吞吐量
  3. 同步移交,避免任务排队,直接将任务移交给工作者线程,将任务放入这类队列时,必须有一个线程正在等待接受,否则如果当前线程池大小小于最大值,则会创建一个线程来接受,否则根据饱和策略,任务将被拒绝,典型的应用是newCachedThreadPool返回的线程池

如果任务之间存在依赖,有界队列会导致饥饿死锁问题,此时需要使用无界队列,例如newCachedThreadPool返回的线程池

2.2.3 饱和策略

JDK提供了几种饱和策略

  1. AbortPolicy,默认的饱和策略,抛出未检查的RejectdExecutionException,调用者捕获并处理
  2. DiscardPolicy,悄悄抛弃任务
  3. DiscardOldestPolicy,悄悄抛弃下一个将要被执行的任务,如果是优先级队列,则抛弃优先级最高的任务,然后重新尝试将新任务入队
  4. CallerRunsPolicy,在主线程,也就是submit的线程中执行,如果是一个web应用,此期间主线程不会accept,到达的请求将被阻塞到tcp层的队列,而不是应用层队列

2.2.4 线程工厂

通过指定线程工厂,可以定制线程池的配置信息

public interface ThreadFactory {
    Thread newThread(Runnable r);
}

有很多场景需要自定义线程工厂,例如:

  1. 为线程指定UncatchtExceptionHandler
  2. 给线程一个更有意义的名字,标识是特定线程池创建的,解释线程转储信息和错误日志
  3. 维护一些统计信息,例如有多少线程被创建和销毁

一个简单的线程工厂的例子

private static class MyThreadFactory implements ThreadFactory {
    private AtomicInteger index = new AtomicInteger(0);
    
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        
        // 设置线程名称
        thread.setName("My-ThreadPool-" + index.getAndIncrement());
        
        return thread;
    }
    
    // 返回已创建线程的数量
    public int getCreatedThreadNum() {
        return index.get();
    }
}

private static void test() {
    BlockingQueue queue = new LinkedBlockingDeque();
    ThreadFactory factory = new MyThreadFactory();
    
    ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 10, 0L, 
            TimeUnit.SECONDS, queue, factory);
    
    int threadNum = 10;
    for (int i = 0; i < threadNum; i++) {
        pool.submit(new Runnable() {
            @Override
        public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
    }
    
    pool.shutdown();
    
    try {
        pool.awaitTermination(600, TimeUnit.SECONDS);
        System.out.println("created thread num: " + ((MyThreadFactory) factory).getCreatedThreadNum());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

运行的输出如下,可以看到线程的名称已经设置好了,同时也可以获取到创建的线程数量

Running My-ThreadPool-0
Running My-ThreadPool-1
Running My-ThreadPool-2
Running My-ThreadPool-3
Running My-ThreadPool-4
Running My-ThreadPool-5
Running My-ThreadPool-6
Running My-ThreadPool-7
Running My-ThreadPool-8
Running My-ThreadPool-9
created thread num: 10

2.2.5 扩展线程池

ThreadPoolExecutor可以被继承,改写beforeExecute, afterExecute, terminated方法。无论任务正常返回,还是抛出异常(Error除外)都会调用afterExecute,线程池完成关闭操作时调用terminated方法,可用于释放各种资源,发送通知、记录日志或者收集统计信息等

一个简单的扩展线程池的例子

private static class MyThreadPoolExecutor extends ThreadPoolExecutor {
    public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, 
                                BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        System.out.println("beforeExecute " + t.getName());
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        System.out.println("afterExecute " + Thread.currentThread().getName());
    }

    @Override
    protected void terminated() {
        super.terminated();
        System.out.println("terminated");
    }
}

private static void test() {
    BlockingQueue queue = new LinkedBlockingDeque();
    ThreadFactory factory = new MyThreadFactory();
    
    ThreadPoolExecutor pool = new MyThreadPoolExecutor(10, 10, 0L, 
            TimeUnit.SECONDS, queue, factory);
    
    int threadNum = 10;
    for (int i = 0; i < threadNum; i++) {
        pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("Running " + Thread.currentThread().getName());
            }
        });
    }
    
    pool.shutdown();
    
    try {
        pool.awaitTermination(600, TimeUnit.SECONDS);
        System.out.println("created thread num: " + ((MyThreadFactory) factory).getCreatedThreadNum());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

运行的输出如下,和2.2.4节相比,此处代码使用了自定义的线程池类MyThreadPoolExecutor,实现了线程执行周期的管理

beforeExecute My-ThreadPool-0
beforeExecute My-ThreadPool-1
beforeExecute My-ThreadPool-2
Running My-ThreadPool-0
Running My-ThreadPool-1
afterExecute My-ThreadPool-0
Running My-ThreadPool-2
afterExecute My-ThreadPool-1
afterExecute My-ThreadPool-2
beforeExecute My-ThreadPool-3
Running My-ThreadPool-3
afterExecute My-ThreadPool-3
beforeExecute My-ThreadPool-4
Running My-ThreadPool-4
afterExecute My-ThreadPool-4
beforeExecute My-ThreadPool-5
Running My-ThreadPool-5
afterExecute My-ThreadPool-5
beforeExecute My-ThreadPool-6
Running My-ThreadPool-6
afterExecute My-ThreadPool-6
beforeExecute My-ThreadPool-7
Running My-ThreadPool-7
afterExecute My-ThreadPool-7
beforeExecute My-ThreadPool-8
Running My-ThreadPool-8
afterExecute My-ThreadPool-8
beforeExecute My-ThreadPool-9
Running My-ThreadPool-9
afterExecute My-ThreadPool-9
terminated
created thread num: 10
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值