传统线程中,一个线程只能运行一次run方法,且运行结束后就被回收了,不能重复利用。如果想运行多个任务,则多少个任务就需要多少个线程,十分不便。此外,线程在运行中随着调度来回切换,易导致资源崩溃。因此,JDK提供了线程池。
线程池(Thread Pool)是池化技术的一种。线程池能够重用线程,降低系统资源消耗,方便管控线程并发数,实现线程的延时和定时功能。
池化技术:不直接创建具体的资源,而是创建一个资源对应的池,在池中创建具体的资源。池会让空闲的资源执行任务。当任务执行完毕后,资源不会被销毁,而是停留在池中,等待下一次任务。池能够重用资源,降低系统资源消耗,方便管理。除了线程池以外,常见的池化技术还包括数据库连接池等。
线程池的本质就是一些空闲的线程和一个任务队列(即一系列实现了Runnable接口、重写了run方法的类对象)。线程池中的空闲的线程会主动执行队列中的任务。任务执行完毕后,核心线程不会被销毁,而是等待下一个任务的执行。只要不断往队列中添加任务,有限的线程就能够执行无穷的任务。
一个线程池
通过创建一个线程池对象从而获得一个线程池。线程池类ThreadPoolExecutor实现了ExecutorService接口。
/*
* 线程池执行器ThreadPoolExecutor的构造器每个参数的含义依次是:
* corePoolSize 核心线程数量
* maximumPoolSize 最大线程的数量
* keepAliveTime 线程池中除核心线程外的其他线程最长保留时间
* util 时间的单位
* workQueue 任务等待队列
*/
ExecutorService excutor = new ThreadPoolExecutor(3, 6, 2,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>()); // 获得一个3核心,至多6线程的线程池
excutor.execute(new Runnable() {
public void run() { /******任务******/ }
}); // 为线程池添加一个任务
for (int i = 0; i < 20; i++) {
excutor.execute(new Runnable() {
public void run() { /******任务******/ }
});
} // 为线程池添加20个任务
excutor.shutdown(); // 待所有线程空闲后,关闭线程池
可以向线程池提交任务,将任务添加到队列中。一旦存在空闲的线程,就会执行队列中的任务。非核心线程空闲后,待保留时间结束后即回收。核心线程不会被回收。收到shutdown()命令后,线程池会等待所有线程空闲后,才关闭线程池。
任务队列
不同的任务队列拥有不同的特性。
BlockingQueue:(继承了Queue的接口)双缓冲队列,内部使用两条队列,允许两个线程同时向队列一个存储,一个取出操作。在保证并发安全的同时,提高了队列的存取效率。
- ArrayBlockingQueue:采用数组实现,具有固定长度的队列。使用构造器时须指定长度。对象采用FIFO排序。
- LinkedBlockingQueue:采用双向链表实现,可指定大小,默认为Integer.MAX_VALUE。对象采用FIFO排序。
- PriorityBlockingQueue:对象依据自然排序或比较器排序的LinkedBlockingQueue。
- SynchronousQueue:是最快的任务队列。队列中只有一个元素,每次删除操作都要等待插入操作,每次插入操作都要等待删除操作。一旦有了插入线程和移除线程,那么很快由插入线程移交给移除线程。这个队列相当于一个通道,本身并不存储元素。
JDK提供的线程池
在实际项目中,一般在使用线程时均会使用线程池,而使用线程池则一般使用JDK提供的线程池。
Java通过java.util.current.Executors类提供四种线程池(静态方法,返回一个线程池对象):
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程;若所有线程均处于运行状态,则新建线程。该线程池创建的都是非核心线程,最大线程数为Integer.MAX_VALUE ,空闲线程存活时间为1分钟,采用最快的SynchronousQueue作为任务队列。可缓存线程池通常用于执行一些生存期很短的异步型任务。
- newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。所有线程都是核心线程,采用LinkedBlockingQueue作为任务队列。线程的数量最好根据CPU能够并行执行的线程数(可通过Runtime.getRuntime().availableProcessors()获取)进行配置。
- newSingleThreadExecutor:创建一个单线程的线程池,它只会用唯一的核心线程来执行任务,保证所有任务按照指定任务队列的顺序执行。采用LinkedBlockingQueue作为任务队列。
- newScheduledThreadPool:创建一个定长线程池,并支持线程进行定时和周期性任务执行。采用DelayedWorkQueue作为任务队列,并拥有自己单独的类ScheduledExecutorService(ExecutorService的子类)。
前三个循环可能的运行结果如下:Runnable r = new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName());
}
}; // 定义一个任务
ExecutorService cachedExe = Executors.newCachedThreadPool(); // 缓存线程池
ExecutorService fixedExe = Executors.newFixedThreadPool(5); // 定长线程池,长度为5
ExecutorService singleExe = Executors.newSingleThreadExecutor(); // 单线程池
for (int i = 0; i < 10; i++) {
cachedExe.execute(r);
}
for (int i = 0; i < 10; i++) {
fixedExe.execute(r);
}
for (int i = 0; i < 10; i++) {
singleExe.execute(r);
}
ScheduledExecutorService sexe = Executors.newScheduledThreadPool(4); // 定时线程池,长度为4
sexe.schedule(r, 3, TimeUnit.SECONDS); // 3秒后执行r任务
sexe.scheduleAtFixedRate(r, 3, 2, TimeUnit.SECONDS); // 3秒后执行r任务,随后每2秒执行一次
cachedExe | fixedExe | singleExe |
---|---|---|
pool-1-thread-1 pool-1-thread-2 pool-1-thread-3 pool-1-thread-4 pool-1-thread-5 pool-1-thread-6 pool-1-thread-4 pool-1-thread-5 pool-1-thread-7 pool-1-thread-6 |
pool-2-thread-1 pool-2-thread-2 pool-2-thread-3 pool-2-thread-3 pool-2-thread-4 pool-2-thread-2 pool-2-thread-1 pool-2-thread-2 pool-2-thread-4 pool-2-thread-5 |
pool-3-thread-1 pool-3-thread-1 pool-3-thread-1 pool-3-thread-1 pool-3-thread-1 pool-3-thread-1 pool-3-thread-1 pool-3-thread-1 pool-3-thread-1 pool-3-thread-1 |
可以看到,当池中的线程均忙时,cachedExe会随着任务的执行而不断创建新线程;fixedExe则最多使用5个线程;而singleExe则只有1个线程。