了解线程池首先就是要知道为什么要用线程池,也就是线程池的优点,下面列出三点。
线程池优点
1.线程的重用
线程的创建和销毁的开销是巨大的,而通过线程池的重用大大减少了这些不必要的开销,当然既然少了这么多消费内存的开销,其线程执行速度也是突飞猛进的提升。
2.控制线程池的并发数
控制线程池的并发数可以有效的避免大量的线程池争夺CPU资源而造成堵塞。
就好比去食堂打饭,一个餐口同一时间只能有一个人打饭,但是排队的人太多,等的时间太长,以至于后面的人想插队抢先打饭,他插队别人肯定不乐意,就打起来了,餐厅大姨看他们打起来也不乐意了,不给他们卖饭了,就造成堵塞了。
3.线程池可以对线程进行管理
线程池可以提供定时、定期、单线程、并发数控制等功能。比如通过ScheduledThreadPool线程池来执行S秒后,每隔N秒执行一次的任务。
ThreadPoolExecutor
在介绍今天的主角之前(四大线程池),先介绍一下ThreadPoolExecutor类的构造方法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
七个参数的意思:
corePoolSize 线程池中核心线程的数量
maximumPoolSize 线程池中最大线程数量
keepAliveTime 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长
unit 有纳秒、微秒、毫秒、秒、分、时、天等
workQueue 线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
threadFactory 为线程池提供创建新线程的功能,这个我们一般使用默认即可
handler 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。
maximumPoolSize(最大线程数) = corePoolSize(核心线程数) + noCorePoolSize(非核心线程数);
核心线程与非核心线程是什么玩意?
非核心线程数:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。
核心线程:固定线程数可闲置不会被销毁,任务来时不用再创建线程,可以直接使用。ThreadPoolExecutor的allowCore’ ThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程,也就是说设置为true时可以理解为核心线程和非核心线程没有了区别。
知道各个参数的意思后我们接着往下怼。
对corePoolSize、maximumPoolSize和workQueue三个参数进行详解
(1)当currentSize<corePoolSize时,没什么好说的,直接启动一个核心线程并执行任务。
(2)当currentSize>=corePoolSize、并且workQueue未满时,添加进来的任务会被安排到workQueue中等待执行。
(3)当workQueue已满,但是currentSize<maximumPoolSize时,会立即开启一个非核心线程来执行任务。
(4)当currentSize>=corePoolSize、workQueue已满、并且currentSize>maximumPoolSize时,调用handler默认抛出RejectExecutionExpection异常。
那ThreadPoolExecutor的构造方法又和四个线程池是什么关系呢?看图说话
可以看出前三个线程池的返回值都有创建ThreadPoolExecutor,当然也就用到了它的参数。而最后一个线程池(ScheduledThreadPool)是ThreadPoolExecutor的子类,自然也就可以调用父类的构造方法了,空口无凭,看看api。
总结一下就是线程池都是直接或间接通过配置ThreadPoolExecutor来实现不同特性的线程池。
四大线程池
1.FixedThreadPool 固定的线程池
有固定数量线程的线程池。其corePoolSize=maximumPoolSize,因为都是核心线程,所以keepAliveTime为0,适合线程稳定的场所。每当提交一个任务就创建一个工作线程,当线程 处于空闲状态时,它们并不会被回收,除非线程池被关闭了,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列(没有大小限制)中。由于newFixedThreadPool只有核心线程并且这些核心线程不会被回收,这样它更加快速地响应外界的请求。
由于设置最大线程是5,所以当执行完这5个线程后,等待两秒后,在执行后面2个线程。
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
//设置最大线程数5个
ExecutorService mFixedThreadPool = Executors.newFixedThreadPool(5);
for(int i = 0;i < 7;i++ ) {
final int index = i;
mFixedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("时间是:"+System.currentTimeMillis()+"第" +index +"个线程" +Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
输出结果:
2.SingleThreadPool 单一的线程池
有固定数量线程的线程池,且数量为一,从数学的角度来看SingleThreadPool应该属于FixedThreadPool的子集。其corePoolSize=maximumPoolSize=1,且keepAliveTime为0,适合线程同步操作的场所。
这类线程池内部只有一个核心线程,以无界队列方式来执行该线程,这使得这些任务之间不需要处理线程同步的问题,它确保所有的任务都在同一个线程中按顺序中执行,并且可以在任意给定的时间不会有多个线程是活动的。
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
ExecutorService mSingleThreadPool = Executors.newSingleThreadExecutor();
for(int i = 0;i < 7;i++) {
final int number = i;
mSingleThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("现在的时间:"+System.currentTimeMillis()+"第"+number+"个线程");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
输出结果:
3.CachedThreadPool 储存的线程池
是一种线程数量不定的线程池,并且其最大线程数为Integer.MAX_VALUE,这个数是很大的,一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。但是线程池中的空闲线程都有超时限制,这个超时时长是60秒,超过60秒闲置线程就会被回收。调用execute将重用以前构造的线程(如果线程可用)。这类线程池比较适合执行大量的耗时较少的任务,当整个线程池都处于闲置状态时,线程池中的线程都会超时被停止。
从结果可以看到,执行第二个任务的时候第一个任务已经完成,会复用执行第一个任务的线程,不用每次新建线程。
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService mCachelThreadPool = Executors.newCachedThreadPool();
for(int i = 0;i < 7;i++ ) {
final int index = i;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mCachelThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("第" +index +"个线程" +Thread.currentThread().getName());
}
});
}
}
}
输出结果:
4.ScheduledThreadPool 计划的线程池
创建一个线程池,它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收,它可安排给定延迟后运行命令或者定期地执行。这类线程池主要用于执行定时任务和具有固定周期的重复任务。
延迟2s执行后每1s执行一次。
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
//设置池中核心数量是2
ScheduledExecutorService mScheduledThreadPool = Executors.newScheduledThreadPool(2);
System.out.println("现在的时间:"+System.currentTimeMillis());
mScheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("现在的时间:"+System.currentTimeMillis());
}
}, 2, 1,TimeUnit.SECONDS);//这里设置延迟2秒后每1秒执行一次
}
}
输出结果:
四大线程池介绍完了,顺便提一下之前提到的线程池队列。
三种阻塞队列:
BlockingQueue workQueue = null;
workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界
workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界
workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界
终于怼完了,写的不好的地方希望各位大佬多多指教。