线程池介绍

1. 简介

假如一个服务器完成一项任务的时间为T:

T1 创建线程的时间
T2 在线程中执行任务的时间,包括线程同步所需要的时间
T3 线程销毁的时间

显然 T= T1+T2+T3. 注意:这是一个理想化的情况

可以看出,T1,T3是多线程自身带来的开销(在Java中,通过映射pThread,并进一步通过SystemCall实现native线程),我们渴望减少T1和T3的时间,从而减少T的时间。但是一些线程的使用者并没有注意到这一点,所以在线程中频繁的创建或者销毁线程,这导致T1和T3在T中占有相当比例。这显然突出的线程池的弱点(T1,T3),而不是有点(并发性)。

所以线程池的技术正是如何关注缩短或调整T1,T3时间的技术,从而提高服务器程序的性能。

  1. 通过对线程进行缓存,减少创建和销毁时间的损失;

  2. 通过控制线程数量的阀值,减少线程过少带来的CPU闲置(比如长时间卡在I/O上了)与线程过多给JVM内存与线程切换时系统调用的压力。

在平时我们可以通过 线程工厂 来创建线程池来尽量避免上述的问题。

线程池介绍_第1张图片

2. 线程池的创建

不管是ThreadPoolExecutor还是SchedduledThreadPoolExecutor,最终都是需要经过ThreadPoolExecutor

/**
corePoolSize:该线程池中核心线程数最大值
maximumPoolSize:该线程池中线程总数的最大值
keepAliveTime:该线程池中非核心线程闲置超时时长
unit:上面时间的单位
workQueue:保存等待执行任务的阻塞队列
threadFactory:可以通过线程工厂给创建出来的线程设置更加有意义的名字
handler:饱和策略。默认为AbortPolicy:表示无法处理新任务时抛出异常
*/
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

  1. corePoolSize:当提交一个任务到线程池时,线程池会创建一个核心线程来执行任务,即使其他空闲的核心线程能够执行新任务也会创建新的核心线程,而等到需要执行的任务数大于线程池核心线程的数量时就不再创建,这里也可以理解为当核心线程的数量等于线程池允许的核心线程最大数量的时候,如果有新任务来,就不会创建新的核心线程。如果你想要提前创建并启动所有的核心线程,可以调用线程池的prestartAllCoreThreads()方法。该方法调用的是 addWorker 方法。

    核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。

    如果指定 ThreadPoolExecutorallowCoreThreadTimeOut 这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间( keepAliveTime),就会被销毁掉。

    //java.util.concurrent.ThreadPoolExecutor.java
     private boolean addWorker(Runnable firstTask, boolean core) {
         ...
         try {
             //创建工作任务
             w = new Worker(firstTask);
             final Thread t = w.thread;
             ...
             //添加到set中
             workers.add(w);
             workerAdded = true;
         }	
         ...
         if (workerAdded) {
             //启动该任务
             t.start();
             workerStarted = true;
         }
     }
    
  2. maximumPoolSize: 最大线程数的数量包含核心线程数。线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。所以只有队列满了的时候,这个参数才有意义。因此当你使用了无界任务队列的时候,这个参数就没有效果了。

  3. keepAliveTime:指代线程活动保持时间,即当线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率,不然线程刚执行完一个任务,还没来得及处理下一个任务,线程就被终止,而需要线程的时候又再次创建,刚创建完不久执行任务后,没多少时间又终止,会导致资源浪费。

    注意:这里指的是核心线程池以外的线程。还可以设置 allowCoreThreadTimeout = true 这样就会让核心线程池中的线程有了存活的时间。

  4. timeUint:上面时间的单位。

  5. 阻塞队列顾名思义。

  6. 线程工厂顾名思义。

  7. RejectedExecutionHandler:指代拒绝执行程序,可以理解为饱和策略:当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是 AbortPolicy,表示无法处理新任务时抛出异常。在JDK1.5中Java线程池框架提供了以下4种策略。

    • AbortPolicy:直接抛出异常 RejectedExecutionException。
    • CallerRunsPolicy:只用调用者所在线程来运行任务,即由调用 execute方法的线程执行该任务。
    • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
    • DiscardPolicy:不处理,丢弃掉,即丢弃且不抛出异常。

3. 线程池的分类

继承结构为:

# Executor -> ExecutorService -> AbstractExecutorService -> ThreadPoolExecutor

几种创建线程池的方法:

//java.util.concurrent.Executors.java

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

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

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, 
                                  new SynchronousQueue());
}

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

public static ScheduledExecutorService newScheduledThreadPool(int var0) {
    return new ScheduledThreadPoolExecutor(var0);
}

从以上代码可知,线程池分为5种:

  1. FixedThreadPool
  2. SingleThreadExecutor
  3. CachedThreadPool
  4. SingleThreadScheduleExecutor
  5. ScheduledThreadPool

其中前3个属于 ThreadPoolExecutor,后2个属于 ScheduledThreadPoolExecutor。

4. 提交执行任务

可以使用两个方法向线程池提交任务,分别是 execute() (Executor中)和 submit() (ExecutorService) 方法。submit 在线程创建小节已举例,此处不多讲。

public class Test implements Runnable {

    @Override
    public void run() {
        System.out.println("现在的Thread id :" + Thread.currentThread().getName());
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args){
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);
        ThreadPoolExecutor executor = 
            new ThreadPoolExecutor(3,5,6000,TimeUnit.MILLISECONDS,queue);
        for (int i = 0; i < 7 ; i ++){
            Runnable runnable = new Test();
            executor.execute(runnable);
        }
        executor.shutdown();
    }
}
/*
现在的Thread id :pool-1-thread-1
现在的Thread id :pool-1-thread-2
现在的Thread id :pool-1-thread-3
现在的Thread id :pool-1-thread-1
现在的Thread id :pool-1-thread-2
现在的Thread id :pool-1-thread-3
现在的Thread id :pool-1-thread-1
*/

虽然我们创建了7个任务,但是只有三个线程在执行。因为我们的任务队列的个数是10个,当任务队列没有满的时候,任务会放在任务队列中。显然3个由核心线程处理,剩下的7个会放在任务队列。这里任务队列还没有满,任务会放在任务队列中。

只有任务队列满了,而且线程池未满的时候,才会创建新的额外的线程去处理任务。这部分的知识会在线程池原理小节讲解。

5. 线程池关闭

有两个方法可以关闭线程池,shutdownshutdownNow方法

原理:遍历线程池中的工作线程,然后逐个调用线程的interrupt方法,但是根据前面说的中断线程的方法中,如果线程使用sleep()wait()方法进入了就绪状态,那么可以使用interrupt()方法是线程离开run()方法,同时结束线程。所以遇到轮询线程,有可能会无法中止。

shutdown是将线程池的状态设置为SHUTDOWN状态,然后遍历任务表,挨个调用线程的interrupt方法

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

shutdownNow 是将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表

public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

线程池在项目中是需要持续工作的全局场景,可以结合具体的场景而定,一般不建议手动关闭线程池

6. 阻塞队列

BlockingQueue 中的几个具体API介绍:

offer(E e):将给定的元素设置到队列中,有返回值true/false;如果限定了长度的队列中设置值,推荐使用

add(E e):将给定的元素设置到队列中,成功返回true,否则抛异常

put(E e):将给定的元素设置到队列中,若没有多余空间,则阻塞,直到有多余空间

take():取,如果没有值,则阻塞

poll():取,如果没有值,则抛异常

poll(long timeOut, TimeUnit unit):取,给定时间没取出来,抛异常

一般来说,workQueue有以下4种队列类型:

  1. SynchronousQueue:同步队列,不保留任务直接提交给线程处理,线程如果不够就会报错,所以会将maximumPoolSize 指定成 Integer.MAX_VALUE,即无限大,去规避这个风险

  2. LinkedBlockingQueue:链表阻塞队列(无界队列)

    • 当前线程数 < 核心线程数,则新建核心线程处理任务;
    • 当前线程数 = 核心线程数,则进入队列等待。
    • 这个队列没有最大值限制,即超过核心线程数的任务都将被添加到队列中,这也就导致maximumPoolSize 设定失效,因为总线程数永远不会超过 corePoolSize
  3. ArrayBlockingQueue:数组阻塞队列,由于是数组可以限定大小,接收到任务的时候,如果没有达到corePoolSize 的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了 maximumPoolSize,并且队列也满了,则发生错误

  4. DelayQueue:延迟队列,队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

实际上,Java中的Executors已经为我们提供了4种线程池:

  1. newFixedThreadPool:创建一个定长线程池,因为采用LinkedBlockingQueue,所以可以控制最大并发数

线程池介绍_第2张图片

  1. newCachedThreadPool:利用SynchronousQueue特性创建,都是非核心线程

线程池介绍_第3张图片

  1. newScheduledThreadPool:创建一个定长任务线程池,支持定时及周期性任务执行,DelayedWorkQueue这边不需要其中的元素实现Delayed接口

    // Executors.java
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    // ScheduledThreadPoolExecutor.java
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }
    
  2. newSingleThreadExecutor:创建1个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行,遵循LinkedBlockingQueue规则(无大小限制,保持等待)

线程池介绍_第4张图片

7. 案例Okhttp

//okhttp3/RealCall.kt
-> enqueue(responseCallback:Callback)

//okhttp3/Dispatcher.kt
-> enqueue(call:AsyncCall)

@get:Synchronized
@get:JvmName("executorService") val executorService: ExecutorService
get() {
    if (executorServiceOrNull == null) {
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 
                                                   60, TimeUnit.SECONDS,
                                                   SynchronousQueue(),
                                                   threadFactory("$okHttpName 
                                                                 Dispatcher", false))
    }
    return executorServiceOrNull!!
}

可以看到 OKhttp 使用的是 SynchronousQueue,那么这个线程池是怎么处理的?还是回到Dispatcher里面看看

var maxRequests = 64 // 并发执行的最大请求数(可配置)
var maxRequestsPerHost = 5 // 表示单个主机地址在某一个时刻的并发请求的最大值(可配置)

如果我们的应用中经常会发起多个请求,如果请求单个主机地址的话,最多只能请求5个

private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()
      while (i.hasNext()) {
        val asyncCall = i.next()

        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
        if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.

        i.remove()
        asyncCall.callsPerHost.incrementAndGet()
        executableCalls.add(asyncCall)
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }

总体来说,当请求任务数大于 maxRequests 并且相同 host 最大请求数大于 maxRequestsPerHost,就会把请求任务放在 readyAsyncCalls 队列里;当线程池里执行任务的 runnable 执行完任务在最后会检查 readyAsyncCalls 里有没有任务,如果有任务并且是同一个 host 就放入到线程池中执行。因此通过这个方法不断地从 readyAsyncCalls 队列里取出任务,对线程池里的线程进行复用。

8. 线程池原理及调度过程

在 ThreadPoolExecutor 有个 ctl 的 AtomicInteger 变量,可以保存两个内容(与 Android 的 MeasureSpec 原理一样)。

  • 所有线程数量
  • 每个线程所处状态
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING,0));

// 得到线程的状态,高3位存runState
private static int runStateOf(int c)     { return c & ~CAPACITY; }

// 得到worker的数量,低29位存线程数
private static int workerCountOf(int c)  { return c & CAPACITY; }

private static int ctlOf(int rs, int wc) { return rs | wc; }
// AbstractExecutorService,添加任务方式的一种
public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
}

// ThreadPoolExecutor ,添加任务方式的一种
public void execute(Runnable command) {
    ... 
    // 取出线程池内线程数目
    int c = ctl.get();
    // 1. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
    if (workerCountOf(c) < corePoolSize) {
       if (addWorker(command, true))
           return;
       c = ctl.get();
    }
    // 2.如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
    if (isRunning(c) && workQueue.offer(command)) {
        ...
        // 把任务已经添加进队列,就不需要再用 Worker来执行任务了
        addWorker(null, false);
        ...
    }
	...
}
private boolean addWorker(Runnable firstTask, boolean core){
    // 两个for循环判断是否需要添加Worker
    ...
    Worker w = null;
      try {
          w = new Worker(firstTask);
          final Thread t = w.thread;
          ...
          // HashSet workers = new HashSet<>()    
          workers.add(w);
          ...
          workerAdded = true;
      }
    ...
    if (workerAdded) {
        // 开启子线程线程调度执行 Worker 内部的 run 方法,
          t.start();
       	  workerStarted = true;
    }
    ...
 }

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable{
    
    final Thread thread;
    Runnable firstTask;
    
    Worker(Runnable firstTask) {
    	this.firstTask=firstTask;
    	getThreadFactory().newThread(this);
    }
    @Overide
    public void run() {
       runWorker(this);
    }
}
// 这个方法的执行是Worker驱动,while循环表达一个线程按照线程池规则可以执行很多Task。
final void runWorker(Worker w) {
	 Runnable task = w.firstTask;
	 w.firstTask = null;
	 try {
	 // 只需要一个Worker运行,就会进入循环。核心线程会立即执行添加的任务
        while (task != null || (task = getTask()) != null) {
        	...
        	try {
               task.run();
            }
        	...
        }
     }
}

private Runnable getTask() {
	...
	 try {
         Runnable r = timed ?
             workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
             workQueue.take();
         if (r != null)
     	    return r;
         timedOut = true;
     } 
	...
}
// Executors
 private static class DefaultThreadFactory implements ThreadFactory {
 
 	public 	Thread newThread(Runnable r){
 		Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),0);
 	}
 }
// ThreadPoolExecutor
private volatile RejectedExecutionHandler handler;
final void reject(Runnable command) {    
    handler.rejectedExecution(command, this);
}

你可能感兴趣的:(java,okhttp)