1.再说线程池之前先了解下线程、用户级线程、内核级线程概念
- 线程可以通过继承Thread,实现Runnable接口以及实现Callable接口来创建线程,它是比进程更小执行单位。
- 线程的生命周期和状态
- 用户级线程:
- 用户线程指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/核心态切换,速度快,操作系统内核不知道多线程的存在,因此一个线程阻塞将使得整个进程(包括它的所有线程)阻塞。由于这里的处理器时间片分配是以进程为基本单位,所以每个线程执行的时间相对减少。
- 优点:当有多个处理机也就是多个CPU时,一个进程的多个线程可以同时执行。
- 缺点:线程切换相对于用户级线程来说更为耗时。
- 内核级线程:
- 由操作系统内核创建和撤销。内核维护进程及线程的上下文信息以及线程切换。一个内核线程由于I/O操作而阻塞,不会影响其它线程的运行。Windows NT和2000/XP支持内核线程。
- 优点:线程的调度不需要内核直接参与,控制简单;创建和销毁线程、线程切换代价等线程管理的代价比内核线程少得多;允许每个进程定制自己的调度算法,线程管理比较灵活。这就是必须自己写管理程序,与内核线程的区别。
- 缺点:资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用。基于时间片轮转调度。
2.什么是线程池
- 它是一种池化技术,我们可以先创建一些线程放在容器中(池中),当任务来的时候,池中的线程就可以直接去处理任务。
3.线程池作用:
- 避免频繁的创建和销毁线程所造成的开销,节约了系统资源。
- 提高程序的响应速度。
- 通过线程池对线程进行统一管理
4.什么时候使用线程池,应用场景?
- 任务量多,但是单个任务的处理时间较短。
5.常见线程池分类
- newCachedThreadPool() //可缓存的线程池
- 没有核心线程,如果任务来了,那么就创建非核心线程去处理任务。如果过了60s以后,还没有任务需要处理,那么非核心线程将会被销毁。适用于大量负载较轻的任务。
- 由于没有核心线程,允许创建线程最大数为Integer.MAX_VALUE;因此当任务数极其多的情况下,会耗尽CPU和内存的资源。使用SynchronousQueue存储任务。
- newFixedThreadPool(int n) //固定大小的线程池
- 线程池最大容量为n,核心线程的数量也为n,只要有任务,那么n个核心线程可以一直复用。任务来了,先判断线程池里面的线程数是否小于核心线程数,如果小于就创建核心线程处理任务。适用于负载较重的服务器。
- 底层使用LinkedBlockingQueue(它是一个无界队列)来存储任务,当工作线程数大于核心线程数以后,新来的任务会加入LinkedBlockingQueue,从而有可能造成OOM。
- newSingleThreadExecutor() //单个线程的线程池
- 核心线程数和线程池容量都为1,也就是一次只能处理一个任务。每次都是去阻塞队列里面取任务有序的处理任务。
- 底层也是用LinkedBlockingQueue存储任务,有可能造成OOM。
- newScheduledThreadPool(int corePoolSize) //可周期性调度的线程池
- 可以延时或者周期性的处理异步任务。
-
使用DelayedWorkQueue存储任务,超时时间为0.
- 底层调用:
public ThreadPoolExecutor(int corePoolSize, //核心线程数
int maximumPoolSize, //线程池容量
long keepAliveTime, //timeout时间,非核心线程存活时间
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;
}
6. 线程池的状态:
- shutdown() VS shutdownNow()
- shutdown():关闭线程池,线程池状态设置为SHUTDOWN。线程池不会接收新任务,但是阻塞队列里面的任务会被执行完。
- shutdownNow():关闭线程池,线程池状态设置为STOP。线程池会终止正在运行的任务,并停止处理阻塞队里里面的任务,它有返回结果,返回的是阻塞队列里面的所有任务(List<Runnable>)。
7. 源码流程分析 ThreadPoolExecutor.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)) { //如果线程池是RUNNING状态并且这个时候还来任务,那么就把任务加入阻塞队列中 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); //核心线程、最大线程数以及阻塞队列都满了,那么就是用拒绝策略来处理任务 }
- 任务来->核心线程处理->加入阻塞队列,如果阻塞队列满了->交给非核心线程(maxPoolSize-corePoolSize)线程处理,如果这一块也满了->使用4种拒绝策略处理
- 拒绝策略
- AbortPolicy -- 当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常。
- CallerRunsPolicy -- 当任务添加到线程池中被拒绝时,会在线程池当前正在运行的Thread线程池中处理被拒绝的任务。
- DiscardOldestPolicy -- 当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。
- DiscardPolicy -- 当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务。
- 线程池默认的处理策略是AbortPolicy!
8.几个常见对比:
- Runnable和Callable?
- Runnable接口不会有任何的返回结果或者异常检查;JDK1.5之后才引入的Callable接口,Callable有返回结果,它是用于处理Runnable接口无法处理的场景。
- submit()和execute的区别?
- submit():执行完以后返回Future类型的对象,我们可以根据该返回值判断任务是否执行完。
- execute():没有返回值,我们不知道任务是否执行完。
9.线程池大小确定
- 在实际开发中,我们应该使用new ThreadPoolExecutor(***,***,***,...,)自定义参数来创建线程池。如何确定线程池大小,主要根据下面两种情况:
- CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
- I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。
10.源码分析之后的疑惑
- ThreadPoolExecutor$Worker的run方法时谁调用的?
- 是ThreadPoolExecutor里面的addWorker(Runnable firstTask, boolean core)方法里面的:w = new Worker(firstTask); final Thread t = w.thread; 之后t.start()调用的。
-
Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); //由ThreadFactory产生线程,该线程创建成功之后调用该线程的start方法会运行Runnable的任务。 //而这里传入的参数是this,也就是Worker的实例,而Worker又实现了Runnable接口,因此会调用Worker的run方法。 } public interface ThreadFactory { /** * Constructs a new {@code Thread}. Implementations may also initialize * priority, name, daemon status, {@code ThreadGroup}, etc. * * @param r a runnable to be executed by new thread instance * @return constructed thread, or {@code null} if the request to * create a thread is rejected */ Thread newThread(Runnable r); //@param r a runnable to be executed by new thread instance }
为了验证上面自己的想法,我本地写了一个验证类:
-
package com.sap.leo; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; //自定义ThreadFactory来创建线程 public class LeoTestThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; public LeoTestThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } @Override public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); if (t.isDaemon()) t.setDaemon(false); if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); return t; } }
package com.sap.leo; import java.util.concurrent.ThreadFactory; import java.util.concurrent.locks.AbstractQueuedSynchronizer; //测试类 public class LeoTest { private volatile static ThreadFactory threadFactory; public static void main(String[] args) throws InterruptedException { TaskDemo task = new TaskDemo(); Worker worker = new Worker(task); Thread t = worker.getT(); t.start(); Thread.sleep(50*1000); } private static class TTT implements Runnable { @Override public void run() { System.out.println("TTT.run()..."); } } private static final class Worker extends AbstractQueuedSynchronizer { private Thread t; private Runnable task; public Worker(Runnable task) { //使用自定义的ThreadFactory来创建线程,这里传递的任务是TTT //因此该线程启动的时候会调用TTT的run方法执行相应的任务 this.t = getThreadFactory().newThread(new TTT()); this.task = task; } public Thread getT() { return t; } public void setT(Thread t) { this.t = t; } public Runnable getTask() { return task; } public void setTask(Runnable task) { this.task = task; } public void run() { runWorker(this); } } public static void runWorker(Worker worker) { Runnable task = worker.getTask(); task.run(); } public static ThreadFactory getThreadFactory() { return new LeoTestThreadFactory(); } }
输出结果:
- 线程是如何实现复用的?
- 在ThreadPoolExecutor的runWorker(Worker w)方法中,由于时候用了while死循环,因此只要阻塞队列不为空,那么创建的工作线程就会一直去阻塞队列里面获取任务来执行,这就实现了线程复用。
-
我们再看看getTask()方法final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; //为何要先解锁??? w.unlock(); // allow interrupts boolean completedAbruptly = true; try { //只要阻塞队列不空那么就从里面取任务 while (task != null || (task = getTask()) != null) { //处理任务过程中需要手动加锁 w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { //处理任务 task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { //任务执行完销毁 task = null; w.completedTasks++; //任务处理完手动解锁 w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
-
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); //如果限定的超神时间,那么就用poll方法获取任务,没有的话就用take方法获取任务。获取完任务会把任务宠阻塞队列里面删除 if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
我们再来看看一个实现的阻塞队列:ArrayBlockingQueue(有界队列),它底层使用Object[]来存储任务的。
- 在runWorker(Worker worker)方法里面,为何任务执行的之前要先解锁?(暂未解决)