创建线程池由工厂类Executors完成。其中newCachedThreadPool,newFixedThreadPool,newSingleThreadExecutor对应的实现类是ThreadPoolExecutor,newScheduledThreadPool对应的实现类是ScheduledThreadPoolExecutor。
ThreadPoolExecutor
-
int corePoolSize 线程池的最小线程数目,即使线程闲置,这些线程也一直保留
-
int maximumPoolSize 线程池中最大线程数目
-
long keepAliveTime 当线程池中的线程超过corePoolSize时,当超过该事件设定的时长线程一直处于闲置状态时,线程将被回收
-
TimeUnit unit 上述值对应的时间单位
-
BlockingQueue workQueue 一个保留Runable对象的阻塞队列。
相关实现可以参考
https://blog.csdn.net/define_us/article/details/80222062 -
ThreadFactory threadFactory 用于创建线程的工厂。该值可以缺省,此时使用的是默认的线程创建工厂。
static class DefaultThreadFactory 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;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
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;
}
}
- RejectedExecutionHandler handler 当线程池中的线程已经达到maximumPoolSize同时BlockingQueue也已经满员时,调用该handler。该值可以缺省,将采用如下默认实现
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
ThreadPoolExecutor该类中,最核心的方法应该就是execute()了。
- 如果当前线程数少于corePoolSize,那么就直接新建线程。无非是JDK基本的线程操作代码。
//构造方法中调用ThreadFactory新建一个线程。并将worker的firstTask指定为当前task。
w = new Worker(firstTask);
final Thread t = w.thread;
t.start();
//加入worker列表
workers.add(w);
-
将命令加入队列。在加入队列后,我们仍然需要确认一下是否还有存活线程。如果没有了,还需要重新创建线程。
-
如果我们不能成功的把命令加入队列,也不能新建线程,那么就只能reject这个命令
完整逻辑如下
//获取当前atomic integer ctl中记载的线程池的状态(实际上用比特操作同时存储了状态和线程数量)。
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
//如果小于,那么增加一个worker,处理新的command
if (addWorker(command, true))
return;
c = ctl.get();
}
//isRunning(c)用于检查是否处于shutdown状态
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);
这里面Worker最难以理解的地方是其继承了AbstractQueuedSynchronizer。可以堪称woker的执行是一种资源。所以不能排出一个worker会被两个thread抢夺(当然从代码逻辑上来讲这是不可能的)。所以需要借助AQS的资源排队机制。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);//新建了一个线程,并把自己Worker作为一个Runable对象传给这个thread。这样,这个thread在start的时候就会执行这个Worker的方法。进一步调用到ThreadPoolExecutor的runwoker方法。
}
注释里写了一个解释
/**
* Class Worker mainly maintains interrupt control state for
* threads running tasks, along with other minor bookkeeping.
* This class opportunistically extends AbstractQueuedSynchronizer
* to simplify acquiring and releasing a lock surrounding each
* task execution. This protects against interrupts that are
* intended to wake up a worker thread waiting for a task from
* instead interrupting a task being run. We implement a simple
* non-reentrant mutual exclusion lock rather than use
* ReentrantLock because we do not want worker tasks to be able to
* reacquire the lock when they invoke pool control methods like
* setCorePoolSize. Additionally, to suppress interrupts until
* the thread actually starts running tasks, we initialize lock
* state to a negative value, and clear it upon start (in
* runWorker).
*/
我们看一下Worker的实际运行过程(ThreadPoolExecutor的runwoker方法)。无非是从队列拿task,拿去执行,如果报出异常就把那么就执行processWorkerExit。这里面我们也可以看到,线程池里的线程,是不会因为抛出异常而死亡的。因为已经捕获了所有的异常。
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);
}
}
JAVA内部的几种线程池
ScheduledThreadPoolExecutor
构造函数如下:
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
ScheduledThreadPoolExecutor继承了ThreadPoolExecutor。核心是使用DelayedWorkQueue。DelayedWorkQueue是在该类内部定义的一个静态内部类,实现了BlockingQueue接口。
首先你需要知道算法,堆可以用来实现优先队列。DelayedWorkQueue就是使用了堆,插入元素使用siftUp方法,移除元素使用siftDown方法。在该对象中,维护了一个Condition。通过available.awaitNanos(delay)来消耗时间。
private final Condition available = lock.newCondition();
public RunnableScheduledFuture<?> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture<?> first = queue[0];
//如果为空则等待通知
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
//到时则出对
if (delay <= 0)
//finishPoll用于在出队时调整堆结构
return finishPoll(first);
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}
任务队列中存储的是ScheduledFutureTask
SingleThreadScheduledExecutor
单例的定时线程池
FixedThreadPool
核心线程数和最大线程数相同
SingleThreadExecutor
一个单线程的线程池
CachedThreadPool
maximumPoolSize设置为无穷大。所以提交任务的速度 > 线程池中线程处理任务的速度就要不断创建新线程。
ForkJoinPool
将一个大任务拆分成多个小任务后,使用fork可以将小任务分发给其他线程同时处理,使用join可以将多个线程处理的结果进行汇总;这实际上就是分治思想的并行版本。
提交的任务必须是的对象RecursiveTask
线程池的强大应用
我们并非是没事闲的增加了线程池的概念。线程池给予了我们强大而常用的功能。
UncaughtExceptionHandler
如果不使用线程池,为了捕获线程抛出的异常,你只能每个线程作如下操作
Thread thread = new Thread(new ThreadTest1());
thread.setUncaughtExceptionHandler(new MyExceptionHandler());
thread.start();
有了线程池,我们可以非常方便的实现这一功能。
ThreadFactory executorThreadFactory = new BasicThreadFactory.Builder()
.namingPattern("task-scanner-executor-%d")
.uncaughtExceptionHandler(new LogUncaughtExceptionHandler(LOGGER))
.build();
Executors.newSingleThreadExecutor(executorThreadFactory);
线程池的关闭
阻止新来的任务提交,对已经提交了的任务不会产生任何影响。当已经提交的任务执行完后,它会将那些闲置的线程(idleWorks)进行中断,这个过程是异步的。
**shutdown()**阻止新来的任务提交,对已经提交了的任务不会产生任何影响。当已经提交的任务执行完后,它会将那些闲置的线程(idleWorks)进行中断,这个过程是异步的。
通过将线程池的状态改成SHUTDOWN,当再将执行execute提交任务时,如果测试到状态不为RUNNING,则抛出rejectedExecution,从而达到阻止新任务提交的目的。
在调用中断任务的方法时,它会检测workers中的任务,如果worker对应的任务没有中断,并且是空闲线程,它才会去中断。另外的话,workQueue中的值,还是按照一定的逻辑顺序不断的往works中进行输送的,这样一来,就可以保证提交的任务按照线程本身的逻辑执行,不受到影响。
**shutdownNow()**会阻止新来的任务提交,同时会中断当前正在运行的线程,即workers中的线程。另外它还将workQueue中的任务给移除,并将这些任务添加到列表中进行返回。通过将线程池的状态改成STOP,当再将执行execute提交任务时,如果测试到状态不为RUNNING,则抛出rejectedExecution,从而达到阻止新任务提交的目的。当执行shutdownNow()方法时,如遇已经激活的任务,并且处于阻塞状态时,shutdownNow()会执行1次中断阻塞的操作,此时对应的线程报InterruptedException,如果后续还要等待某个资源,则按正常逻辑等待某个资源的到达。例如,一个线程正在sleep状态中,此时执行shutdownNow(),它向该线程发起interrupt()请求,而sleep()方法遇到有interrupt()请求时,会抛出InterruptedException(),并继续往下执行。在这里要提醒注意的是,在激活的任务中,如果有多个sleep(),该方法只会中断第一个sleep(),而后面的仍然按照正常的执行逻辑进行。
FAQ
- 线程池的大概工作过程
下面这个代码会打印begin job多少次。
public class ThreadPoolTest {
public static void main(String[] args) {
System.out.println("Hello,world");
ExecutorService threadPoolExecutor = new ThreadPoolExecutor(5,20,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2));
for (int i = 0; i < 10; i++) {
threadPoolExecutor.submit(new Job());
}
}
static class Job implements Runnable {
@Override
public void run() {
System.out.println("begin job");
try{
Object obj = new Object();
synchronized (obj){
obj.wait();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
答案是八次。随着前五次任务提交,线程池依次启动了五个核心线程。第6个和第7个任务会进入缓冲队列。第8,第9,第10个因为队列已经满了,直接新建线程执行。然而,因为所有线程都会被阻塞,永远不会退出,所以,最终只能执行8次。缓冲队列中的两个线程因为没有线程退出而永远执行不到。(所以你也看到了这种情况下不保证线程池对任务按照提交顺序执行)。