Java多线程-6-线程池

六、线程池

1、使用优点

  1. 降低资源消耗

    可以重复利用已创建的线程,降低线程创建和销毁造成的消耗

  2. 提高响应速度

    当任务到达时,可以不用等到线程创建就能立即执行

  3. 提高线程的可管理性

    线程是稀缺资源,无限地创建,不仅会消耗系统资源,还会降低系统的稳定性。而使用线程池可以进行统一分配、调优和监控

2、线程池的创建

通过java.util.concurrent.ThreadPoolExecutor来创建

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

corePoolSize:核心线程数

maximumPoolSize:最大线程数(包括核心线程数)

keepAliveTime:非核心线程的存活时间(数值)。非核心线程(空闲状态)等待执行新的任务的终结最大时长(wait for new tasks before terminating)java.lang.Thread.State: WAITING (parking)

unit:非核心线程的存活时间(单位)

workQueue:线程任务队列

threadFactory:生成线程的方法工厂。比如使用默认的静态方法创建的线程工厂java.util.concurrent.Executors#defaultThreadFactory

handler:线程任务拒绝策略。比如使用默认的拒绝执行策略java.util.concurrent.ThreadPoolExecutor.AbortPolicy

/* 会直接抛出异常 */
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
    throw new RejectedExecutionException("Task " + r.toString() +
                                         " rejected from " +
                                         e.toString());
}

3、工作方式

  1. 当执行线程任务时,先判断是否达到核心线程数(corePoolSize),如果没有达到,则会创建新的线程来执行线程任务,否则就将线程任务加入到阻塞队列中(BlockingQueue)
  2. 当阻塞队列已满时,并且还未达到最大线程数(maximumPoolSize)时,会创建新的线程来执行线程任务
  3. 当达到最大线程数时,如果还需要执行线程任务,则会执行拒绝策略(handler)
  4. 当额外的线程(非核心线程)空闲时,且达到了最大等待时间,那么它们就会被终结(TERMINATED

注意:当创建新的线程时,需要获取全局锁

/**
 * Lock held on access to workers set and related bookkeeping.
 * While we could use a concurrent set of some sort, it turns out
 * to be generally preferable to use a lock. Among the reasons is
 * that this serializes interruptIdleWorkers, which avoids
 * unnecessary interrupt storms, especially during shutdown.
 * Otherwise exiting threads would concurrently interrupt those
 * that have not yet interrupted. It also simplifies some of the
 * associated statistics bookkeeping of largestPoolSize etc. We
 * also hold mainLock on shutdown and shutdownNow, for the sake of
 * ensuring workers set is stable while separately checking
 * permission to interrupt and actually interrupting.
 */
private final ReentrantLock mainLock = new ReentrantLock();


private boolean addWorker(Runnable firstTask, boolean core) {
    
    

    // ... ...

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
    
    
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
    
    
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
    
    
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
    
    
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
    
    
                mainLock.unlock();
            }
            if (workerAdded) {
    
    
                t.start();
                workerStarted = true;
            }
        }
    } finally {
    
    
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

虽然可以使用并发的手段,但是最好还是使用锁。因为一方面可以确保线程安全,使得线程池运行稳定;另一方面也简化了一些操作(线程池源码实现)

4、最佳实践

  1. 合理配置最大线程数
    • CPU密集型任务:CPU核数 + 1【减少线程的切换】
    • IO密集型任务 [计算方式一]:CPU核数 × 2
    • IO密集型任务 [计算方式二]:CPU核数 / (1 - 阻塞系数),阻塞系数在 0.8 ~ 0.9之间【提高吞吐量】
  2. 合理配置非核心线程的存活时间(keepAliveTime),假如需要执行的任务有很多,且任务执行时间较短,则可以调大这个时间,以提高线程的利用率
  3. 合理配置阻塞队列的大小,如果太大,不仅无法执行到任务,还有可能耗尽内存资源
  4. 关闭线程池有2种方法,shutdown是只关闭线程池,但未完成的任务还是会让其执行完;shutdownNow是关闭线程池,但未完成的也要终结

猜你喜欢

转载自blog.csdn.net/adsl624153/article/details/103865353