13_线程池的实际操作

  • 参考 https://www.cnblogs.com/kuoAT/p/6714762.html

  1. 线程池一般就是这么用的

     public static void main(String[] args) {
    
         ExecutorService executorService = Executors.newFixedThreadPool(8);
     
         Future<Integer> future = executorService.submit(new Callable<Integer>() {
             @Override
             public Integer call() throws Exception {    
                 // doSomething();
                 return null;
             }
         });
     
         try {
             System.out.println(future.get());
         } catch (InterruptedException | ExecutionException e) {
             e.printStackTrace();
         }
     }
    

    也就是说 submit 一个任务,然后用 future 对象来异步获取结果

  2. 几个类和接口的继承关系是

    Executor <— ExecutorService <— AbstractExecutorService <— ThreadPoolExecutor

    而创建线程池的时候使用的其实就是 ThreadPoolExecutor

         public static ExecutorService newFixedThreadPool(int nThreads) {
             
             return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
         }
    
  3. 进入 submit 方法是这样的

     public <T> Future<T> submit(Callable<T> task) {
     
         if (task == null) 
             throw new NullPointerException();
     
         RunnableFuture<T> ftask = newTaskFor(task);
    
         execute(ftask);
     
         return ftask;
     }
    

    可以看到 submit 方法其实是给 execute 方法套了一个壳子,然后返回一个 future 对象,所以重点还是要看 execute 方法

  4. 在了解将任务提交给线程池到任务执行完毕整个过程之前,我们先来看一下 ThreadPoolExecutor 类中其他的一些比较重要成员变量

     public class ThreadPoolExecutor extends AbstractExecutorService {
    
         ...
    
         private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));   // 用来代表有效线程数量和线程池状态
    
         private final BlockingQueue<Runnable> workQueue;  // 任务缓存队列,用来存放等待执行的任务
    
         private final ReentrantLock mainLock = new ReentrantLock();  // 线程池的主要状态锁,对线程池状态(比如线程池大小、runState等)的改变都要使用这个锁
    
         private final HashSet<Worker> workers = new HashSet<Worker>();  // 用来存放工作集
    
         private final Condition termination = mainLock.newCondition();  // 终止线程池时的条件变量
    
         private volatile long keepAliveTime;  // 线程存活时间
    
         private volatile boolean allowCoreThreadTimeOut;   // 是否允许为核心线程设置存活时间
    
         private volatile int corePoolSize;  //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
    
         private volatile int maximumPoolSize;   // 线程池最大能容忍的线程数
    
         private volatile int poolSize;       // 线程池中当前的线程数
    
         private volatile RejectedExecutionHandler handler;   // 任务拒绝策略
    
         private volatile ThreadFactory threadFactory;   // 线程工厂,用来创建线程
    
         private int largestPoolSize;   // 用来记录线程池中曾经出现过的最大线程数
    
         private long completedTaskCount;   // 用来记录已经执行完毕的任务个数
    
         ...
     }
    
  5. 接下来看 execute 方法

     public void execute(Runnable command) {
    
         if (command == null)
             throw new NullPointerException();
     
         /*
          * Proceed in 3 steps:
          *
          * 1. If fewer than corePoolSize threads are running, try to
          * start a new thread with the given command as its first
          * task.  The call to addWorker atomically checks runState and
          * workerCount, and so prevents false alarms that would add
          * threads when it shouldn't, by returning false.
          *
          * 2. If a task can be successfully queued, then we still need
          * to double-check whether we should have added a thread
          * (because existing ones died since last checking) or that
          * the pool shut down since entry into this method. So we
          * recheck state and if necessary roll back the enqueuing if
          * stopped, or start a new thread if there are none.
          *
          * 3. If we cannot queue task, then we try to add a new
          * thread.  If it fails, we know we are shut down or saturated
          * and so reject the task.
          */
    
         int c = ctl.get();   // 获取工作线程数量
         
         // 情况一: 工作线程数量小于核心线程池
         if (workerCountOf(c) < corePoolSize) {
             if (addWorker(command, true))
                 return;
         
             c = ctl.get();
         }
    
         // 情况二:向阻塞队列中添加
         if (isRunning(c) && workQueue.offer(command)) {
    
             int recheck = ctl.get();
    
             if (! isRunning(recheck) && remove(command))
                 reject(command);
             else if (workerCountOf(recheck) == 0)
                 addWorker(null, false);
         }
    
         // 情况三:试图开线程,看看是否超过最大线程池,失败了执行拒绝策略
         else if (!addWorker(command, false))
             reject(command);
     }
    

    这里面 addWorker 会开新线程

  • addWorker 方法

      private boolean addWorker(Runnable firstTask, boolean core) {
    
          retry:
          for (int c = ctl.get();;) {
              // Check if queue empty only if necessary.
              if (runStateAtLeast(c, SHUTDOWN)
                  && (runStateAtLeast(c, STOP)
                      || firstTask != null
                      || workQueue.isEmpty()))
                  return false;
    
              for (;;) {
                  if (workerCountOf(c)
                      >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                      return false;
              
                  if (compareAndIncrementWorkerCount(c))
                      break retry;
                  c = ctl.get();  // Re-read ctl
              
                  if (runStateAtLeast(c, SHUTDOWN))
                      continue retry;
                  // else CAS failed due to workerCount change; retry inner loop
              }
          }
    
          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 c = ctl.get();
    
                      if (isRunning(c) ||
                          (runStateLessThan(c, STOP) && 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;
      }
    

    这个方法前半部分是各种 CAS 操作,后半部分是在加锁的前提下,尝试添加worker,让worker的新线程start

  • reject 方法用到了策略模式,将四种基本的拒绝策略封装好交给各自实现

      final void reject(Runnable command) {
          handler.rejectedExecution(command, this);
      }
    
### Java 线程池大小设置的最佳践 在设计和现基于线程池的应用程序时,合理设置线程池的大小对于系统的性能至关重要。以下是关于如何正确设置 Java 线程池大小的一些最佳践: #### 1. 考虑 CPU 密集型任务 如果应用程序主要执行的是 CPU 密集型任务,则应将线程池的大小设置为 `CPU 核心数` 或稍大一些。这是因为过多的线程会引发上下文切换开销,从而降低整体效率。 计算公式如下: \[ \text{线程池大小} = \text{可用处理器数量} + 1 \] 例如,在一台拥有 8 核 CPU 的服务器上,可以考虑将线程池大小设置为 9 左右[^1]。 #### 2. 针对 I/O 密集型任务 当任务涉及大量的输入/输出作(如文件读写、网络通信等)时,由于这些任务通常会让线程处于等待状态,因此可以增加线程的数量以提高吞吐量。 一种常见的经验法则为: \[ \text{线程池大小} = (\text{核心数} \times (1 + W/C)) \] 其中 \(W\) 是等待时间,\(C\) 是际工作时间[^3]。 假设每项任务有 90% 的时间用于等待数据传输完成,而仅花费 10% 的时间进行有效运算,那么理论上可创建多达十倍于物理核数的线程数目。 #### 3. 动态调整策略 考虑到不同环境下的负载变化可能很大,静态设定固定数值未必总是最优解法;此时采用动态调节机制便显得尤为重要——即依据时监控到的各项指标自动增减活动中的工作者线程例总数目。这可以通过自定义扩展 ThreadPoolExecutor 类并重载其相应方法来达成目标[^2]。 #### 4. 使用合适的队列长度 除了控制最大允许并发执行的工作单元外,还需要注意待处理请求排队等候区域容量限制问题。过长的任务序列可能导致内存耗尽风险加剧;反之则容易造成资源闲置浪费现象发生。故此需综合考量具体应用场景需求后再做决定。 ```java import java.util.concurrent.*; public class CustomThreadPool { public static void main(String[] args) throws InterruptedException { int corePoolSize = Runtime.getRuntime().availableProcessors() * 2; int maximumPoolSize = corePoolSize + 1; ExecutorService executor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), new ThreadFactoryBuilder().setNameFormat("custom-thread-%d").build(), new ThreadPoolExecutor.AbortPolicy() ); // Submit tasks here... executor.shutdown(); } } ``` 上述代码片段展示了如何通过 ThreadPoolExecutor 构造函数显式指定多个重要属性值,包括但不限于初始启动的核心线程计数、最高许可界限以及超出部分该如何处置等等细节方面内容。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值