为何需要线程池?
创建或者关闭线程需要时间,且线程本身需要占用内存资源,因此生产环境线程的数量必须得到控制,盲目的创建线程对系统性能有影响。
什么是线程池?
为了让线程复用、提高响应速度、统一维护管理线程,在线程池中维护几个常用的线程,当需要时从池子中取出一个空闲线程,当完成工作时将线程退回到线程池中。
Java中已提供些线程池?
Executors 框架提供了多个方法来创建线程池,我们也可以自己新建一个 ThreadPoolExecutor 线程池。下面列举几个常见的创建线程池的方法。
public static ExecutorService newFixedThreadPool(int nThreads);
public static ExecutorService newSingleThreadExecutor();
public static ExecutorService newCachedThreadPool();
public static ScheduledExecutorService newSingleThreadScheduledExecutor(int corePoolSize);
- newFixedThreadPool 返回一个固定线程数量的线程池。初始化线程池的时候线程就已创建,有新任务提交时,如果线程池中有空闲线程则执行此任务,若没有,则此任务进任务队列保存。
- newSingleThreadExecutor 返回一个只有一个线程的线程池。所有提交的任务按照FIFO的顺序执行。
- newCachedThreadPool 返回一个可根据实际情况调整线程数量的线程池。若没有线程可用,有任务提交就创建一个线程去执行。线程如果长时间没有任务执行,会经过特定的时间后自动被移除。
- newSingleThreadScheduledExecutor 返回一个单一线程可定时调度任务的线程池。
设置线程池的大小
按照《并发编程实战》一书中的介绍,线程池的最优大小等于:
CPU的数量 * CPU期望的利用率 * (1 + 任务的等待时间/计算时间)
举一个简单的例子:初始化核心线程数为3的固定线程池,启动十个线程,每个线程模拟执行1s
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
executorService.submit(new MyThread());
}
}
static class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ " threadId = " + Thread.currentThread().getId());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
打印结果
pool-1-thread-1 threadId = 12
pool-1-thread-2 threadId = 13
pool-1-thread-3 threadId = 14
pool-1-thread-1 threadId = 12
pool-1-thread-3 threadId = 14
pool-1-thread-2 threadId = 13
pool-1-thread-3 threadId = 14
pool-1-thread-1 threadId = 12
pool-1-thread-2 threadId = 13
pool-1-thread-3 threadId = 14
可以看到上面的结果:三个线程被复用了,因线程睡眠了一秒,每次都只会有三个线程同时执行。
先了解下线程池是什么设计类结构的,看下他们之间的关系
各自提供的方法
接口类 Executor 定义了一个接口 execute() :在未来某个时间执行给定的 command。这个命令可能会在新线程中执行,也有可能在线程池中的线程或者正调用中的线程执行,这取决于 Executor 的实现类。
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
ExecutorService 被设计用来管理线程池:submit 方法通过创建并返回一个 Future 接口扩展了 Executor.execute(Runnable),Future 能够被用来取消执行或者等待执行完成。
//有序的关闭线程池,之前提交的任务依旧执行,但是不接受新任务。如果已经关闭,再次调用无影响。
void shutdown();
//立即关闭线程池,尝试关闭所有执行中的线程,不处理等待中的任务,并返回等待中的任务列表。
//不保证一定能够停止执行中的任务,例如通过 Thread.interrupt 中断线程,线程不响应中断的就不会被终止。
List<Runnable> shutdownNow();
//线程池已经被关闭返回 true
boolean isShutdown();
//如果所有任务完成关闭后返回 true,注意除非先调用 shutdown 或者 shutdownNow,否者 isTerminated 永远不会返回 true
boolean isTerminated();
//在一个关闭请求之后阻塞等待所有任务完成执行,若所有任务执行完成返回 true,如果超时返回 false,执行中断阻塞。
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
//提交一个带返回值的任务,并返回包含了运行结果的 Future,可以通过调用 Future.get() 等待获取返回值
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
//执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
抽象类 AbstractExecutorService 用来提供 ExecutorService 的默认实现。此类通过调用 newTaskFor 方法返回的一个 RunnableFuture 接口来实现 submit、invokeAny 和 invokeAll ,默认使用 FutureTask 作为返回的实例。
//使用 FutureTask 包装了 Runnable 或 Callable
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
//调用 execute(RunnableFuture) 来提交任务,并返回 RunnableFuture
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return doInvokeAny(tasks, true, unit.toNanos(timeout));
}
关于 FutureTask ,见FutureTask详解。
正式分析线程池源码之前,回顾下线程的基本特性。
线程的状态,看下 Thread 类内部的枚举
public enum State {
//新建
NEW,
//可运行,不区分运行中和等待CPU调度
RUNNABLE,
//阻塞,等待获取监视器锁
BLOCKED,
//等待
WAITING,
//等待一定的时间后自动恢复
TIMED_WAITING,
//结束
TERMINATED;
}
线程中断相关
//中断线程,只是设置一个中断标识而已,有调用者决定是否中断
public void interrupt(){
...
//调用本地方法设置中断状态
interrupt0();
}
private native void interrupt0();
//判断是否中断,并清除中断状态
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
//判断是否中断
public boolean isInterrupted() {
return isInterrupted(false);
}
//调用本地方法获取中断状态
private native boolean isInterrupted(boolean ClearInterrupted);
等待(wait)和通知(notify)线程
一个线程调用 object.wait() 方法,那么他就会进入 object 的等待队列。当其他线程调用 object.notify() 方法时,它就会从等待队列中随机选择一个线程并将其唤醒。调用这两个方法前都需要获取到对象的监视器。
等待线程结束 join
线程1调用 线程2的 join 方法,那么线程1一直阻塞,直到线程2执行完毕。
让出CPU执行条件 yield
暂时放弃CPU执行条件,之后还是会竞争CPU然后执行。
守护线程
一个Java程序里面如果只有守护线程,那么JVM将退出。调用线程的 setDaemon(true) 设置守护线程。
线程优先级
Java的线程优先级与操作系统有关,按照正常的逻辑,优先级数字越低的线程越能够获取到CPU,但无法保证一定这样。