线程池的概念
线程池是一种多线程处理形式,处理过程中将任务添加队列,然后在创建线程后自动启动这些任务,每个线程都使用默认的堆栈大小,以默认的优先级运行,并处在多线程单元中,如果某个线程在托管代码中空闲,则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后辅助线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才能启动。
java里面的线程池的顶级接口是Executor,Executor并不是一个线程池,而只是一个执行线程的工具,而真正的线程池是ExecutorService。
线程池的种类
以下摘抄https://blog.csdn.net/qq_33453910/article/details/81413285
https://blog.csdn.net/guo20082200/article/details/79881294
1.newCachedThreadPool创建一个可缓存线程池程
2.newFixedThreadPool 创建一个定长线程池
3.newScheduledThreadPool 创建一个定长线程池
4.newSingleThreadExecutor 创建一个单线程化的线程池
1.newCachedThreadPool
这类线程池的特点就是里面没有核心线程,全是非核心线程,其maximumPoolSize设置为Integer.MAX_VALUE,线程可以无限创建,当线程池中的线程都处于活动状态的时候,线程池会创建新的线程来处理新任务,否则会用空闲的线程来处理新任务,这类线程池的空闲线程都是有超时机制的,keepAliveTime在这里是有效的,时长为60秒,超过60秒的空闲线程就会被回收,当线程池都处于闲置状态时,线程池中的线程都会因为超时而被回收,所以几乎不会占用什么系统资源。任务队列采用的是SynchronousQueue,这个队列是无法插入任务的,一有任务立即执行,所以CachedThreadPool比较适合任务量大但耗时少的任务。
底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
适用:执行很多短期异步的小程序或者负载较轻的服务器
实例代码:
public class PoolExecutorTest {
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());
}
});
}
}
}
输出结果:
从结果可以看到,执行第二个任务的时候第一个任务已经完成,会复用执行第一个任务的线程,不用每次新建线程。
执行流程如下:
- 没有核心线程,直接向
SynchronousQueue
中提交任务 - 如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个
- 执行完任务的线程有 60 秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则关闭。
2.newFixedThreadPool
这类线程池的特点就是里面全是核心线程,没有非核心线程,也没有超时机制,任务大小也是没有限制的,数量固定,即使是空闲状态,线程不会被回收,除非线程池被关闭,从构造方法也可以看出来,只有两个参数,一个是指定的核心线程数,一个是线程工厂,keepAliveTime无效。任务队列采用了无界的阻塞队列LinkedBlockingQueue,执行execute方法的时候,运行的线程没有达到corePoolSize就创建核心线程执行任务,否则就阻塞在任务队列中,有空闲线程的时候去取任务执行。由于该线程池线程数固定,且不被回收,线程与线程池的生命周期同步,所以适用于任务量比较固定但耗时长的任务。
底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue() 无解阻塞队列
通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:执行长期的任务,性能好很多
实例代码:
public class PoolExecutorTest {
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();
}
}
});
}
}
}
输出结果:
由于设置最大线程是5,所以当执行完这5个线程后,等待两秒后,在执行后面2个线程。
线程池执行任务的流程如下:
- 线程数少于核心线程数,也就是设置的线程数时,新建线程执行任务
- 线程数等于核心线程数后,将任务加入阻塞队列;由于队列容量非常大,可以一直加加加
- 执行完任务的线程反复去队列中取任务执行
3.newScheduledThreadPool
这类线程池核心线程数量是固定的,但是它的非核心线程是没有限制的,并且非核心线程一闲置就会被回收,keepAliveTime同样无效,因为核心线程是不会回收的,当运行的线程数没有达到corePoolSize的时候,就新建线程去DelayedWorkQueue中取ScheduledFutureTask然后才去执行任务,否则就把任务添加到DelayedWorkQueue,DelayedWorkQueue会将任务排序,按新建一个非核心线程顺序执行,执行完线程就回收,然后循环。任务队列采用的DelayedWorkQueue是个无界的队列,延时执行队列任务。综合来说,这类线程池适用于执行定时任务和具体固定周期的重复任务。
底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue() 无解阻塞队列
通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:一个任务一个任务执行的场景
延迟执行实例代码:
public class PoolExecutorTest {
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.schedule(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("现在的时间:"+System.currentTimeMillis());
}
}, 4, TimeUnit.SECONDS);//这里设置延迟4秒执行
}
}
执行的结果如下:
误差可以忽略,实际结果确实延迟了4秒执行。
定期执行示例代码
public class PoolExecutorTest {
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, 3,TimeUnit.SECONDS);//这里设置延迟2秒后每3秒执行一次
}
}
执行的结果如下:
可发现确实延迟2秒后每隔3秒后就会执行一次,程序不退出就一直执行下去。
执行流程如下:
- 调用上面两个方法添加一个任务
- 线程池中的线程从 DelayQueue 中取任务
- 然后执行任务
具体执行任务的步骤也比较复杂:
- 线程从 DelayQueue 中获取 time 大于等于当前时间的 ScheduledFutureTask ----
DelayQueue.take()
- 执行完后修改这个 task 的 time 为下次被执行的时间
- 然后再把这个 task 放回队列中 ----
DelayQueue.add()
4.newSingleThreadExecutor
这类线程池顾名思义就是一个只有一个核心线程的线程池,从构造方法来看,它可以单独执行,也可以与周期线程池结合用。其任务队列是LinkedBlockingQueue,这是个无界的阻塞队列,因为线程池里只有一个线程,就确保所有的任务都在同一个线程中顺序执行,这样就不需要处理线程同步的问题。这类线程池适用于多个任务顺序执行的场景。
底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
适用:周期性执行任务的场景
示例代码:
public class PoolExecutorTest {
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();
}
}
});
}
}
}
执行的结果如下:
可发现是有顺序地去执行上面6个线程。
执行顺序:
- 程池中没有线程时,新建一个线程执行任务
- 有一个线程以后,将任务加入阻塞队列,不停加加加
- 唯一的这一个线程不停地去队列里取任务执行
补充:阿里编程规范中是不允许使用Executors来创建线程池,以下是阿里规范原文
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,
这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
Positive example 1:
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
Positive example 2:
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
Positive example 3:
<bean id="userThreadPool"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="2000" />
<property name="threadFactory" value= threadFactory />
<property name="rejectedExecutionHandler">
<ref local="rejectedExecutionHandler" />
</property>
</bean>
//in code
userThreadPool.execute(thread);