java多线程-概念&创建启动&中断&守护线程&优先级&线程状态(多线程编程之一)
java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)
java&android线程池-Executor框架之ThreadPoolExcutor&ScheduledThreadPoolExecutor浅析(多线程编程之三)
Java多线程:Callable、Future和FutureTask浅析(多线程编程之四)无论是在java还是在android中其实使用到的线程池都基本是一样的,因此本篇我们将来认识一下线程池Executor框架(相关知识点结合了并发编程艺术书以及Android开发艺术探索而总结),下面是本篇的主要知识点:
1.Executor框架浅析
首先我们得明白一个 问题,为什么需要线程池?在java中,使用线程来执行异步任务时,线程的创建和销毁需要一定的开销,如果我们为每一个任务创建一个新的线程来执行的话,那么这些线程的创建与销毁将消耗大量的计算资源。同时为每一个任务创建一个新线程来执行,这样的方式可能会使处于高负荷状态的应用最终崩溃。所以线程池的出现为解决这个问题带来曙光。我们将在线程池中创建若干条线程,当有任务需要执行时就从该线程池中获取一条线程来执行任务,如果一时间任务过多,超出线程池的线程数量,那么后面的线程任务就进入一个等待队列进行等待,直到线程池有线程处于空闲时才从等待队列获取要执行的任务进行处理,以此循环.....这样就大大减少了线程创建和销毁的开销,也会缓解我们的应用处于超负荷时的情况。
1.1Executor框架的两级调度模型
在java线程启动时会创建一个本地操作系统线程,当该java线程终止时,这个操作系统线程也会被回收。而每一个java线程都会被一对一映射为本地操作系统的线程,操作系统会调度所有的线程并将它们分别给可用的CPU。而所谓的映射方式是这样实现的,在上层,java多线程程序通过把应用分为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。这样种两级调度模型如下图所示:
从图中我们可以看出,应用程序通过Executor框架控制上层的调度,而下层的调度由操作系统内核控制,下层的调度不受应用程序的控制。
1.2 Executor框架的结构
Executor框架的结构主要包括3个部分
1.任务:包括被执行任务需要实现的接口:Runnable接口或Callable接口
2.任务的执行:包括任务执行机制的核心接口Executor,以及继承自Executor的EexcutorService接口。Exrcutor有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)。
3.异步计算的结果:包括接口Future和实现Future接口的FutureTask类(这个我们放在下一篇文章说明)
下面我们通过一个UML图来认识一下这些类间的关系:
Extecutor是一个接口,它是Executor框架的基础,它将任务的提交与任务的执行分离开来。
ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。
ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。
ScheduledThreadPoolExecutor比Timer更灵活,功能更强大。
Future接口和实现Future接口的FutureTask类,代表异步计算的结果。
Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或者
ScheduledThreadPoolExecutor执行。区别就是Runnable无法返回执行结果,而Callable可以返回执行结果。
下面我们通过一张图来理解它们间的执行关系:
分析说明:
主线程首先创建实现Runnable或Callable接口的任务对象,工具类Executors可以把一个Runnable对象封装为一个Callable对象,使用如下两种方式:
Executors.callable(Runnable task)或者Executors.callable(Runnable task,Object resule)。
然后可以把Runnable对象直接提交给ExecutorService执行,方法为ExecutorService.execute(Runnable command);或者也可以把Runnable对象或者Callable对象提交给ExecutorService执行,方法为ExecutorService.submit(Runnable task)或ExecutorService.submit(Callable task)。这里需要注意的是如果执行
ExecutorService.submit(...),
ExecutorService将返回一个实现Future接口的对象(其实就是FutureTask)。当然由于FutureTask实现了Runnable接口,我们也可以直接创建FutureTask,然后提交给ExecutorService执行。到此Executor框架的主要体系结构我们都介绍完了,我们对此有了大概了解后,下面我们就重点聊聊两个主要的线程池实现类。
2.ThreadPoolExecutor浅析
ThreadPoolExecutor是线程的真正实现,通常使用工厂类Executors来创建,但它的构造方法提供了一系列参数来配置线程池,下面我们就先介绍
ThreadPoolExecutor的构造方法中各个参数的含义。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
ExecutorService fixedThreadPool=Executors.newFixedThreadPool(5);
我们来看看
FixedThreadPool创建方法源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
FixedThreadPool的corePoolSize和
maximumPoolSize参数都被设置为nThreads。当线程池中的线程数量大于
corePoolSize时,keepAliveTime为非核心空闲线程等待新任务的最长时间,超过这个时间后非核心线程将被终止,这里
keepAliveTime设置为0L,就说明非核心线程会立即被终止。事实上这里也没有非核心线程创建,因为核心线程数和最大线程数都一样的。
下面我们来看看FixedThreadPool的execute()方法的运行流程:
public class LiftOff implements Runnable{
protected int countDown = 10; //Default
private static int taskCount = 0;
private final int id = taskCount++;
public LiftOff() {}
public LiftOff(int countDown) {
this.countDown = countDown;
}
public String status() {
return "#" + id + "(" +
(countDown > 0 ? countDown : "LiftOff!") + ") ";
}
@Override
public void run() {
while(countDown-- > 0) {
System.out.print(status());
Thread.yield();
}
}
}
声明一个Runnable对象,使用
FixedThreadPool执行任务如下:
public class FixedThreadPool {
public static void main(String[] args) {
//三个线程来执行五个任务
ExecutorService exec = Executors.newFixedThreadPool(3);
for(int i = 0; i < 5; i++) {
exec.execute(new LiftOff());
}
exec.shutdown();
}
}
ExecutorService cachedThreadPool=Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
从该静态方法,我们可以看到
CachedThreadPool的corePoolSize被设置为0,而
maximumPoolSize被设置
Integer.MAX_VALUE,即
maximumPoolSize是无界的,而keepAliveTime被设置为60L,单位为妙。也就是空闲线程等待时间最长为60秒,超过该时间将会被终止。而且在这里
CachedThreadPool使用的是没有容量的SynchronousQueue作为线程池的工作队列,但其
maximumPoolSize是无界的,也就是意味着如果主线程提交任务的速度高于
maximumPoolSize中线程处理任务的速度时
CachedThreadPool将会不断的创建新的线程,在极端情况下,
CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。
CachedThreadPool
的execute()方法的运行流程:
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i = 0; i < 10; i++) {
exec.execute(new LiftOff());
}
exec.shutdown();
}
}
SingleThreadExecutor模式只会创建一个线程。它和FixedThreadPool比较类似,不过线程数是一个。如果多个任务被提交给SingleThreadExecutor的话,那么这些任务会被保存在一个队列中,并且会按照任务提交的顺序,一个先执行完成再执行另外一个线程。SingleThreadExecutor模式可以保证只有一个任务会被执行。这种特点可以被用来处理共享资源的问题而不需要考虑同步的问题。
ExecutorService singleThreadExecutor=Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
从静态方法可以看出SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1,其他参数则与FixedThreadPool相同。SingleThreadExecutor使用的工作队列也是无界队列LinkedBlockingQueue。由于
SingleThreadExecutor采用无界队列的对线程池的影响与FixedThreadPool一样,这里就不过多描述了。同样的我们先来看看其运行流程:
public class SingleThreadExecutor {
public static void main(String[] args) {
ExecutorService exec = Executors.newSingleThreadExecutor();
for (int i = 0; i < 2; i++) {
exec.execute(new LiftOff());
}
}
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
创建
SingleThreadScheduledExecutor
的方法构造如下:
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
创建实例对象代码如下:
ScheduledExecutorService scheduledThreadPoolExecutor=Executors.newScheduledThreadPool(5);
ScheduledExecutorService singleThreadScheduledExecutor=Executors.newSingleThreadScheduledExecutor();
public ScheduledFuture> schedule(Runnable command,long delay, TimeUnit unit);
package com.zejian.Executor;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author zejian
* @time 2016年3月14日 下午9:10:41
* @decrition 创建一个工作线程继承Runnable
*/
public class WorkerThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" Start. Time = "+getNowDate());
threadSleep();
System.out.println(Thread.currentThread().getName()+" End. Time = "+getNowDate());
}
/**
* 睡3秒
*/
public void threadSleep(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 获取现在时间
*
* @return 返回时间类型 yyyy-MM-dd HH:mm:ss
*/
public static String getNowDate() {
Date currentTime = new Date();
SimpleDateFormat formatter;
formatter = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss");
String ctime = formatter.format(currentTime);
return ctime;
}
}
执行类如下:
package com.zejian.Executor;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author zejian
* @time 2016年3月14日 下午9:27:06
* @decrition 执行类
*/
public class ScheduledThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
try {
//schedule to run after sometime
System.out.println("Current Time = "+getNowDate());
for(int i=0; i<3; i++){
Thread.sleep(1000);
WorkerThread worker = new WorkerThread();
//延迟10秒后执行
scheduledThreadPool.schedule(worker, 10, TimeUnit.SECONDS);
}
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduledThreadPool.shutdown();
while(!scheduledThreadPool.isTerminated()){
//wait for all tasks to finish
}
System.out.println("Finished all threads");
}
/**
* 获取现在时间
*
* @return 返回时间类型 yyyy-MM-dd HH:mm:ss
*/
public static String getNowDate() {
Date currentTime = new Date();
SimpleDateFormat formatter;
formatter = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss");
String ctime = formatter.format(currentTime);
return ctime;
}
}
运行输入执行结果:
线程任务确实在10秒延迟后才开始执行。这就是schedule()方法的使用。下面我们再介绍2个可用于周期性执行任务的方法。
public ScheduledFuture> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)
scheduleAtFixedRate方法的作用是预定在初始的延迟结束后,周期性地执行给定的任务,周期长度为period,其中initialDelay为初始延迟。
(按照固定的时间来执行,即:到点执行)
public ScheduledFuture> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
package com.zejian.Executor;
import java.util.Date;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author zejian
* @time 2016年3月14日 下午10:05:07
* @decrition 周期函数测试类
*/
public class ScheduledTask {
public ScheduledThreadPoolExecutor se = new ScheduledThreadPoolExecutor(5);
public static void main(String[] args) {
new ScheduledTask();
}
public void fixedPeriodSchedule() {
// 设定可以循环执行的runnable,初始延迟为0,这里设置的任务的间隔为5秒
for(int i=0;i<5;i++){
se.scheduleAtFixedRate(new FixedSchedule(), 0, 5, TimeUnit.SECONDS);
}
}
public ScheduledTask() {
fixedPeriodSchedule();
}
class FixedSchedule implements Runnable {
public void run() {
System.out.println("当前线程:"+Thread.currentThread().getName()+" 当前时间:"+new Date(System.currentTimeMillis()));
}
}
}
当前线程:pool-1-thread-5 当前时间:Tue Aug 08 09:43:18 CST 2017
当前线程:pool-1-thread-4 当前时间:Tue Aug 08 09:43:18 CST 2017
当前线程:pool-1-thread-3 当前时间:Tue Aug 08 09:43:18 CST 2017
当前线程:pool-1-thread-1 当前时间:Tue Aug 08 09:43:18 CST 2017
当前线程:pool-1-thread-2 当前时间:Tue Aug 08 09:43:18 CST 2017
当前线程:pool-1-thread-1 当前时间:Tue Aug 08 09:43:23 CST 2017
当前线程:pool-1-thread-4 当前时间:Tue Aug 08 09:43:23 CST 2017
当前线程:pool-1-thread-3 当前时间:Tue Aug 08 09:43:23 CST 2017
当前线程:pool-1-thread-5 当前时间:Tue Aug 08 09:43:23 CST 2017
当前线程:pool-1-thread-2 当前时间:Tue Aug 08 09:43:23 CST 2017
当前线程:pool-1-thread-1 当前时间:Tue Aug 08 09:43:28 CST 2017
当前线程:pool-1-thread-4 当前时间:Tue Aug 08 09:43:28 CST 2017
当前线程:pool-1-thread-5 当前时间:Tue Aug 08 09:43:28 CST 2017
当前线程:pool-1-thread-3 当前时间:Tue Aug 08 09:43:28 CST 2017
当前线程:pool-1-thread-1 当前时间:Tue Aug 08 09:43:28 CST 2017
java核心技术卷1
android开发艺术探索
java并发编程的艺术