目录
1.1JDK对线程池的支持
为了更好的能够控制多线程,JDK提供了一套Executor框架,其本质是一个线程池。
上面是Executor的框架图。
如图Executors类扮演着线程池工厂的角色,通过Executors可以取得一个拥有特定功能的线程池。从图中看到ThreadPoolExecutor类实现了Executor接口,因此通过这个接口任何Runnable的对象都可以调用。
Executor提供了以下工厂方法:
- newFixedThreadPool():该方法返回一个固定线程数量的线程池。线程池里若有空闲线程则立即执行,否则暂存在一个任务队列中,等待空闲线程。
- newSingleThreadExecutor():该方法返回了一个只有一个线程的线程池。
- newCachedThreadPool():该方法返回了一个可根据实际情况调整线程数量的线程。线程池里若有空闲线程则立即执行,否则会创建一个新的线程。
- newSingleThreadScheduleExecutor():该方法返回一个ScheduleExecutorService对象,线程池大小为1。ScheduleExecutorService接口在ExecutorService接口之上,扩展了在给定时间执行某任务的功能。
- newScheduleThreadPool():该方法也返回一个ScheduleExecutorService接口,但该线程池可以指定线程池数量。
ScheduleExecutorService对象主要的一些方法:
- schedule():会在给定时间,对任务进行一次调度。
- scheduleAtFixedRate():创建一个周期任务。任务开始于给定初始延时。后续的任务按照给定的周期执行:后续第一个任务会在initialDelay+period时执行,后续第二个任务会在initialDelay+2*period时执行,以此类推。
- scheduleWithFixedDelay():创建并执行一个周期性任务。任务开始于初始延时时间,后续任务将会按照给定的延时进行,即上一个任务结束到下一个任务开始的时间差进行。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduleExecutorServoceDemo {
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
ses.scheduleAtFixedRate(new Runnable() {
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(1000);
System.out.println(System.currentTimeMillis()/1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, 1, 2, TimeUnit.SECONDS);
}
}
运行结果:
1537947034
1537947036
1537947038
1537947040
1537947042
可以看到这里时间间隔是2秒。
如果执行时间超过调度时间会发生什么事情。将上述Thread.sleep(1000);改为
Thread.sleep(3000);
运行结果:
1537947606
1537947609
1537947612
1537947615
发现运行任务的执行周期为3秒。也就是说周期小于执行时间,那么任务就会在上一个任务结束后立即被调用。
如果采用scheduleWithFixedDelay(),执行3秒,调度两秒,那么时间间隔为5秒。
1.2核心线程池的内部实现
首先了解一下ThreadPoolExecutor最重要的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:指定了线程池中的线程数量。
- maximumPoolSize:制定了线程池中最大线程的数量。
- keepAliveTime:当线程池数量超过corePoolSize时,多余的空闲线程的存活时间。即,超过corePoolSize的空闲线程,在多长时间内被销毁。
- unit:keepAliveTime的单位。
-
workQueue:任务队列,被提交但尚未被执行的任务。
workQueue,是一个BlockingQueue接口的对象,仅用于存放Runnable对象。在ThreadPoolExecutor的构造函数中可食用以下几种BlockingQueue。
- 直接提交的队列:该功能由SynchronousQueue对象提供。SynchronousQueue是一个特殊的BlockingQueue。它没有容量,每一个插入操作都要等待一个相应的删除操作,反之每一个删除操作都要等待一个对应的插入操作。使用SynchronousQueue,提交的任务不会被真实保存,而总是将新任务交给线程执行,如果没有空闲线程,则创建新线程,如果线程数量达到最大值,则执行拒绝策略。
- 有界的任务队列:该队列可由ArrayBlockingQueue实现。它的构造函数必须带一个容量参数。表示该队列最大容量。有界队列当在任务线程满的时候才能将线程数提升到corePoolSize以上。
- 无界的任务队列:无界的任务队列可通过LinkedBlockingQueue类实现。与有界任务队列相比,除非系统资源耗尽,否则不会加入失败。
- 优先任务队列:优先任务队列是带有执行优先级的任务队列。他通过PriorityBlockingQueue实现,可以控制任务的执行先后顺序。它是一个特殊的无界队列。
-
threadFactory:用于创建线程,一般用默认的即可。
-
handler:拒绝策略。当任务太多来不及处理,如何拒绝任务。
ThreadPoolExecutor的核心调度代码
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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);
}
代码第五行workerCountOf()函数取得了当前线程池的线程总数。当前线程总数小于corePoolSize时,将会通过addWorker()方法直接调度执行。否则在第十行代码处(workQueue.offer())进入等待队列。如果进入等待队列失败,则会执行第17行,将任务直接交给线程池。如果当前线程数已达到maximumPoolSize,则提交失败,就执行18行的拒绝策略。
上图是ThreadPoolExecutor的任务调度逻辑
1.3拒绝策略
JDK内置的拒绝策略如下。
- AbortPolicy策略:该策略会直接抛出异常,组织系统工作。
- CallerRunsPolicy策略:只要线程池关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是任务提交的性能会急剧下降。
- DiscardOldestPolicy策略:该策略丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。
- DiscardPolicy策略:该策略默默丢弃无法处理的任务,不予任何处理。