文章目录
1、什么是线程池?
java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。真正的线程池实现类是ExecutorService。
线程池是一种处理多线程的形式。处理过程中将任务添加队列,然后在创建线程之后启动这些任务,每个线程都有默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。
如果某个线程在托管代码中空闲,则线程池将插入另一个辅助线程来使所有处理器保持繁忙。
如果所有线程池都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后辅助线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才能启动。
线程池的作用优点:线程池可以提高程序的性能,加强对线程的统一管理。
(1)重用线程池的线程,避免因为线程的创建和销毁锁带来的性能开销
(2)有效控制线程池的最大并发数,避免大量的线程之间因抢占系统资源而阻塞
(3)能够对线程进行简单的管理,并提供一下特定的操作如:可以提供定时、定期、单线程、并发数控制等功能
2、创建线程池有哪几种方式?
(1)newCachedThreadPool——可缓存的线程池程
一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。但是线程池中的空闲线程都有超时限制,这个超时时长是60秒,超过60秒闲置线程就会被回收。
比较适合执行大量的耗时较少的任务,当整个线程池都处于闲置状态时,线程池中的线程都会超时被停止。调用execute将重用以前构造的线程(如果线程可用),所以可以实现线程重用。
public class MynewCachedThreadPool {
public static void main(String[] args) {
int num = 9; // 线程数
// CountDownLatch是一个同步辅助类也可以使用AtomicInteger替代
CountDownLatch doneSignal = new CountDownLatch(num);
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < num; i++)
// 在未来某个时间执行给定的命令
// 这里的CountDownLatch的构造函数中使用的int型变量的意思是需要等待多少个操作 的完成。
pool.execute(new WorkerRunnable(doneSignal, i));
try {
doneSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 子线程执行完毕,可以开始后续任务处理了
System.out.println("所有任务执行完毕");
}
}
class WorkerRunnable implements Runnable {
private final CountDownLatch doneSignal;// 同步辅助类
private final int i;// 计数
WorkerRunnable(CountDownLatch doneSignal, int i) {
this.doneSignal = doneSignal;
this.i = i;
}
public void run() {
// 子线程的任务
try {
doWork(i);
} catch (Exception e) {
e.printStackTrace();
}
// 任务执行完毕递减锁存器的计数
doneSignal.countDown();
}
void doWork(int i) {
System.out.println("这是第" + (i + 1) + "个任务");
}
}
(2)newFixedThreadPool ——指定工作线程数量的线程池
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
(3)newScheduledThreadPool ——定长线程池
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
可以分析一下对比Timer类。
(4)newSingleThreadExecutor ——单线程化的线程池
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。
如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
3、线程池有哪几种状态?
线程池中有5种状态:Running(运行)、ShutDown(关机)、Stop(停止)、Tidying(整理)、Terminated(终止)。
(1)Running
能够接收新任务,以及对已添加的任务进行处理。
(2)ShutDown
不接收新任务,但能处理已添加的任务。
调用线程池的shutdown()接口时,线程池状态由running转为shutdown。
(3)Stop
不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
调用线程池的shutdownNow()接口时,线程池由running或者shutdown转为stop。
(4)Tidying
当所有的任务已终止,ctl记录的任务数量为0,线程池会变为TIDYING状态。当线程池变为Tidying状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为Tidying时,进行相应的处理;可以通过重载terminated()函数来实现。
当线程池在shutdown状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 shutdown 转为Tidying。
当线程池在Stop状态下,线程池中执行的任务为空时,就会由Stop转为Tidying。
(5)Terminated
线程池彻底终止。
线程池处在Tidying状态时,执行完terminated()之后,就会由 Tidying状态转为Terminated。
4、线程池中submit()和excute()方法有什么区别?
(1)所属接口不同。
submit(Callable task)、submit(Runnable task, T result)、submit(Runnable task)归属于ExecutorService接口。
execute(Runnable command)归属于Executor接口。ExecutorService继承了Executor。
(2)是否有返回值。
submit()有返回值。
execute没有返回值。
(3)异常捕获。
submit()方便做异常处理。通过Future.get()可捕获异常。