在java 中我们会一般要求创建线程必须使用线程池,因为这样可以避免资源消耗,通过重复利用已经创建的线程来降低线程创建和销毁所造成的消耗, 其次当任务到达时任务可以不用等到线程创建就立即执行,最后可以提高线程的管理性,管控线程,不让线程无限制创建.
我们可以使用 ThreadPoolExecutor 创建线程池
这里我们不适用Executors 来创建线程池,是因为,Executors 使用默认参数不用自定义参数,
自己经常使用Executors提供的工厂方法创建线程池,所以忽略了线程池内部的实现,同时可以规避规避资源耗尽的风险.
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
milliseconds,runnableTaskQueue, handler);
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队 列按 FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按 FIFO (先进先出) 排序元素,吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool()使用了这个队列。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必 须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态, 吞吐量通常要高于 LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
我们可以使用 execute 提交的任务,但是 execute 方法没有返回值,所以无法判 断任务是否被线程池执行成功。通过以下代码可知 execute 方法输入的任务是一 个 Runnable 类的实例。
threadsPool.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
});
可以使用 submit 方法来提交任务,它会返回一个 future,那么我们可以 通过这个 future 来判断任务是否执行成功,通过 future 的 get 方法来获取返回 值, get 方法会阻塞住直到任务完成,而使用 get(long timeout, TimeUnit unit) 方法则会阻塞一段时间后立即返回,这时有可能任务没有执行完。
Future<Object> future = executor.submit(harReturnValuetask);
try {
Object s = future.get();
} catch (InterruptedException e) {
// 处理中断异常
} catch (ExecutionException e) {
// 处理无法执行任务异常
2} finally {
// 关闭线程池
executor.shutdown();
}
调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池, 它们 的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断 线程,所以无法响应中断的任务可能永远无法终止。
shutdown 和 shutdownNow 的区别
shutdownNow 首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或 暂停任务的线程,并返回等待执行任务的列表,而 shutdown 只是将线程池的状 态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。
通常调用 shutdown 来关闭线程池,表示任务执行完关闭,如果任务不一定要执行完, 则可以调用 shutdownNow。
提交一个任务到线程池,首先判断基本线程池是否已满,没有满不管线程池中有没有空闲线程都创建一个新线程来执行任务, 判断工作队列是否已满,没满则将新提交的任务存储在工作队列, 满了, 判断整个线程池是否已满, 没满, 则创建一个新的工作线程来 执行任务,满了,则交给饱和策略来处理这个任务.
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//如果线程数小于基本线程数,则创建线程并执行当前任务
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
//如线程数大于等于基本线程数或线程创建失败,则将当前任务放到工作队
列中。
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
//如果线程池不处于运行中或任务无法放入队列,并且当前线程数量小于最
大允许的线程数量,
则创建一个线程执行任务。
else if (!addIfUnderMaximumPoolSize(command))
//抛出 RejectedExecutionException 异常
reject(command); // is shutdown or saturated
}
}
工作线程: 线程池创建线程时,会将线程封装成工作线程 Worker,Worker 在执 行完任务后,还会无限循环获取工作队列里的任务来执行。
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
任务的性质:CPU 密集型任务,IO 密集型任务和混合型任务。 任务的优先级:高,中和低。 任务的执行时间:长,中和短。 任务的依赖性:是否依赖其他系统资源,如数据库连接。
通过继承线程池并重写线程池的 beforeExecute, afterExecute 和 terminated 方法,我们可以在任务执行前,执行后和线程池关 闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。