在我们开发中经常会使用到多线程,比如在Android中,网络请求或一些耗时操作必须放在子线程中运行,往往会通过Thread开启一个子线程去执行耗时操作,待子线程执行完毕后再通过Handler切换到主线程中运行;如果N多次创建子线程,就无法管理所创建的子线程,它们相互之间竞争,很有可能由于占用过多资源而导致死机或者OOM。所以java提供了线程池来管理我们创建的线程。
线程池的优势:
1. 降低资源消耗。通过重复利用已经创建的线程降低线程的创建与销毁造成的消耗。
2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3. 提高线程的可管理性。线程是稀缺资源,如果无限制第创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
ThreadPoolExecutor
可以通过ThreadPoolExecutor来创建一个线程池,ThreadPoolExecutor类一共用4个构造函数。
ExecutorService threadPool = new ThreadPoolExecutor( …… );
下面是拥有最多参数的构造函数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
……
}
1. corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建
一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
2. maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且创建的线程数小于最大线程数,则线程池会再创建新的执行任务。
3. keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。
4. TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒等
5. workQueue(线程池中保存等待执行任务的阻塞队列):通过线程池中的execute方法提交的Runable对象会存储在该队列中。我们可以选择下面几个阻塞队列:
阻塞队列 | 说明 |
ArrayBlockingQueue | 基于数组实现的有界阻塞队列,该队列按照FIFO(先进先出)原则对队列中的元素进行排序 |
LinkedBlockingQueue | 基于链表实现的阻塞队列,该队列按照FIFO(先进先出)原则对队列中的元素进行排序。 |
SyschronousQueue | 内部没有任何容量的阻塞队列。在它内部没有任何的缓存空间,对于该队列中的数据元素只有当我们试着取走的时候才可能存在 |
PriorityBlockingQueue | 具有优先级的无限阻塞队列 |
可以通过实现BlockingQueue接口来自定义所需的阻塞队列。
6. ThreadFactory(线程工厂,为线程池提供新线程的创建):可以给每个创建出来的线程设置名字。ThreadFactory是一个接口,里面只有一个newThread方法。
7. RejectedExecutionHandler(饱和策略):RejectedExecutionHandler是一个接口,里面只有rejectedExecution(Runnable r, ThreadPoolExecutor executor)方法。当任务队列已满并且线程池中的活动线程已经达到所限定的最大值或者无法成功执行任务,这时候ThreadPoolExecutor 会调用RejectedExecutionHandler中的rejectedExecution方法。在线程池中默认是AbortPolicy,在无法处理新任务是会抛出RejectedExecutionException异常。此外还有3中策略,分别是:
可选值 | 说明 |
CallerRunsPolicy | 用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度 |
DiscardPolicy | 不能执行的任务,并将该任务删除 |
DiscardOldestPolicy | 丢弃队列最近的任务,并执行当前任务 |
ThreadPoolExecutor的使用
1. 创建:一个拥有5个核心线程,最大能容入10个线程的线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10,
10, TimeUnit.SECONDS, new ArrayBlockingQueue(10));
2. 线程池的任务提交有两种方式:execute方法和submit方法。就是将线程放入线程池
execute方法:没有返回值,无法判断任务是否被线程池执行成功。
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程:"+ Thread.currentThread().getName());
}
});
submit方法:使用submit方法提交任务,会返回一个Future(接口)对象,可以通过该对象来判断任务是否执行成功,通过还可以改对象的get方法来获取返回值。如果子线程任务没有执行完毕,会一直阻塞在get无参方法中直到执行完成。如果使用getget(long timeout, TimeUnit unit)方法则会阻塞等待所设的时间值,这时可能子线程中任务还没执行完毕。关于Future的介绍点击打开链接
final int result=i;
Future<Integer> future = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("线程:"+Thread.currentThread().getName());
return result;
}
});
try {
Integer integer = future.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
关于sbumit(Callable)源码
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
从源码上可看出,把Callable实例作为参数传入,生成FutureTask对象,然后把这个对象当成一个Runnable,作为参数另起线程。
3. 线程池的关闭
关闭线程池提供了两个方法:shutdown()
、
shutdownNow()
shutdown:将线程池状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
shutdownNow:将线程池的状态设置成STOP状态,然后中断所有任务(包括正在执行的线程)的线程,并且返回执行任务列表。
例子:
public class ThreadPoolTest {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 10,
TimeUnit.SECONDS, new ArrayBlockingQueue(10));
for (int i = 0; i < 10; i++) {
//提交任务方法一:execute方法
executor.execute(new Runnable() {
@Override
public void run() {
System.out
.println("线程:" + Thread.currentThread().getName());
}
});
//提交任务方法一:submit方法
final int result = i;
Future<Integer> future = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out
.println("线程:" + Thread.currentThread().getName());
return result;
}
});
try {
Integer integer = future.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
executor.shutdown();
}
}
execute方法的执行结果:
线程:pool-1-thread-1
线程:pool-1-thread-2
线程:pool-1-thread-2
线程:pool-1-thread-2
线程:pool-1-thread-2
线程:pool-1-thread-2
线程:pool-1-thread-2
线程:pool-1-thread-3
线程:pool-1-thread-5
线程:pool-1-thread-4
submit方法的执行结果:
线程:pool-1-thread-1
0
线程:pool-1-thread-2
1
线程:pool-1-thread-3
2
线程:pool-1-thread-4
3
线程:pool-1-thread-5
4
线程:pool-1-thread-1
5
线程:pool-1-thread-2
6
线程:pool-1-thread-3
7
线程:pool-1-thread-4
8
线程:pool-1-thread-5
9
线程池的执行流程
1. 提交任务后,首先会判断核心线程数是否已满,如果在线程池中的线程数量没有达到核心线程数,这时会创建核心线程来执行任务;否则,进入下一步操作。
2. 接着判断线程池任务队列是否已满。如果没满,则将任务添加到任务队列中;否则,进入下一步操作。
3. 接着判断线程池中的线程数是否达到最大线程数(执行这步的前提是任务队列已满了)。如果未达到,则创建非核心线程执行任务;否则,就执行饱和策略,默认会抛出RejectedExecutionException异常。
线程类种类
java提供了常见四种线程池,分别可以通过Executors类获取:
FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadExecutor。
FixedThreadPool
通过Executors中的newFixedThreadPool方法来创建,该线程池是一种线程数量固定的线程池。
ExecutorService service = Executors.newFixedThreadPool(4);
在这个线程池中 所容纳最大的线程数就是我们设置的核心线程数。 如果线程池的线程处于空闲状态的话,它们并不会被回收,除非是这个线程池被关闭。如果所有的线程都处于活动状态的话,新任务就会处于等待状态,直到有线程空闲出来。
由于newFixedThreadPool只有核心线程,并且这些线程都不会被回收,也就是 它能够更快速的响应外界请求 。从下面的newFixedThreadPool方法的实现可以看出,newFixedThreadPool只有核心线程,并且不存在超时机制,采用LinkedBlockingQueue,所以对于任务队列的大小也是没有限制的。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
CachedThreadPool
通过Executors中的newCachedThreadPool方法来创建。
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
通过newCachedThreadPool方法在这里我们可以看出它的 核心线程数为0, 线程池的最大线程数Integer.MAX_VALUE。而Integer.MAX_VALUE是一个很大的数,也差不多可以说 这个线程池中的最大线程数可以任意大。当线程池中的线程都处于活动状态的时候,线程池就会创建一个新的线程来处理任务。该线程池中的线程超时时长为60秒,所以当线程处于闲置状态超过60秒的时候便会被回收。 这也就意味着若是整个线程池的线程都处于闲置状态超过60秒以后,在newCachedThreadPool线程池中是不存在任何线程的,所以这时候它几乎不占用任何的系统资源。
对于newCachedThreadPool他的任务队列采用的是SynchronousQueue,上面说到在SynchronousQueue内部没有任何容量的阻塞队列。SynchronousQueue内部相当于一个空集合,我们无法将一个任务插入到SynchronousQueue中。所以说在线程池中如果现有线程无法接收任务,将会创建新的线程来执行任务。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
ScheduledThreadPool
通过Executors中的newScheduledThreadPool方法来创建。
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);
它的核心线程数是固定的,对于非核心线程几乎可以说是没有限制的,并且当非核心线程处于限制状态的时候就会立即被回收。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
SingleThreadExecutor
通过Executors中的newSingleThreadExecutor方法来创建
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
在这个线程池中只有一个核心线程,对于任务队列没有大小限制,也就意味着这一个任务处于活动状态时,其他任务都会在任务队列中排队等候依次执行。
newSingleThreadExecutor将所有的外界任务统一到一个线程中支持,所以在这个任务执行之间我们不需要处理线程同步的问题。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}