2020年Java原创面试题库连载中
【002期】JavaSE面试题(二):基本数据类型与访问修饰符
【003期】JavaSE面试题(三):JavaSE语法(1)
【004期】JavaSE面试题(四):JavaSE语法(3)
【020期】JavaSE系列面试题汇总(共18篇)
【022期】JavaWeb面试题(三):Cookie和Session
【024期】JavaWeb面试题(五):Filter和Listener
【032期】JavaEE面试题(四)Spring(2)
【035期】JavaEE面试题(七)SpringBoot(1)
更多内容,点击上面蓝字查看
并发编程必不可少的线程池,接下来分两篇文章介绍线程池,本文是第一篇。线程池将介绍如下内容:
并发编程可以高效利用CPU资源,提升任务执行效率,但是多线程及线程间的切换也伴随着资源的消耗。当遇到单个任务处理时间比较短,但需要处理的任务数量很大时,线程会频繁的创建销毁,大量的时间和资源都会浪费在线程的创建和销毁上,效率很低。
这个时候就需要用的线程池了,线程作为一个工作者,线程执行完一个任务之后不销毁,而是继续执行其他的任务。
先通过一个简单的示例了解下线程池:
public class Test {
public static void main(String[] args) {
// 1. 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
for (int i = 0; i < 15; i++) {
// 2. 创建任务
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("执行任务...");
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
executor.execute(task);// 3. 任务交给线程池执行
}
executor.shutdown();// 4. 关闭线程池
}
}
Executor框架提供了一种“任务提交”与“任务如何运行”分离开来的机制,实现对异步任务的控制与执行。我们先大概了解下每个类的基本情况。
Executor接口只有一个execute方法,用于提交任务。
public interface Executor {
void execute(Runnable command);
}
// 启动线程执行任务
new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
}
}).start();
// 使用Executor提交任务
Executor executor = newExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
ExecutorService接口继承自Executor接口,提供了线程池主要功能,提交任务、异步任务执行、关闭线程池等。
public interface ExecutorService extends Executor {
// 关闭线程池,已提交的任务继续执行,不接受继续提交新任务
void shutdown();
// 关闭线程池,尝试停止正在执行的所有任务,不接受继续提交新任务
List<Runnable> shutdownNow();
// 线程池是否已关闭
boolean isShutdown();
// 如果调用了 shutdown() 或 shutdownNow() 方法后,所有任务结束了,那么返回true
boolean isTerminated();
// 提交一个 Callable 任务
<T> Future<T> submit(Callable<T> task);
// 提交一个 Runnable 任务,第二个参数将会放到 Future中,作为返回值,
<T> Future<T> submit(Runnable task, T result);
// 提交一个 Runnable 任务
Future<?> submit(Runnable task);
// 执行所有任务,返回 Future 类型的一个 list
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
}
AbstractExecutorService实现了ExecutorService接口,并在其基础上实现了几个实用的方法提供给子类进行调用。
public abstract class AbstractExecutorService implements ExecutorService {
/**
* newTaskFor 方法用于将我们的任务包装成 FutureTask 提交到线程池中执行
* RunnableFuture 是用于获取执行结果的,我们常用它的子类 FutureTask
*/
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);
}
/**
* 提交任务
*/
public Future<?> submit(Runnable task) {
if (task == null)
throw new NullPointerException();
// 1. 将任务包装成 FutureTask
RunnableFuture<Void> ftask = newTaskFor(task, null);
// 2. 交给执行器执行
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
// 1. 将任务包装成 FutureTask
RunnableFuture<T> ftask = newTaskFor(task, result);
// 2. 交给执行器执行
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null)
throw new NullPointerException();
// 1. 将任务包装成 FutureTask
RunnableFuture<T> ftask = newTaskFor(task);
// 2. 交给执行器执行
execute(ftask);
return ftask;
}
// 将 tasks 集合中的任务提交到线程池执行,任意一个线程执行完后就可以结束了
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
try {
return doInvokeAny(tasks, false, 0);
} catch (TimeoutException cannotHappen) {
assert false;
return null;
}
}
// 执行所有的任务,返回任务结果。
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {}
}
ThreadPoolExecutor就是线程池了,继承自AbstractExecutorService。
线程状态如何保存呢?
ThreadPoolExecutor采用一个 32 位的整数(int变量ctl)来存放线程池的状态和当前池中的线程数,其中高 3 位用于存放线程池状态,低 29 位表示线程数。
// 高 3 位用于存放线程池状态,低 29 位表示线程数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
/** 后29位用于存放线程数 */
private static final int COUNT_BITS = Integer.SIZE - 3;
// 000 11111111111111111111111111111
// 最大线程数:这里得到的是 29 个 1,也就是说线程池的最大线程数是 2^29-1=536870911
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
/** 高 3 位表示线程池的状态 */
// 111 00000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
// 000 00000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 001 00000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
// 010 00000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
// 011 00000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
// 将整数 c 的低 29 位修改为 0,获取线程池的状态
return c & ~CAPACITY;
}
// 将整数 c 的高 3 为修改为 0,获取线程池中的线程数
private static int workerCountOf(int c) {
return c & CAPACITY;
}
线程池ThreadPoolExecutor类有四个构造方法,我们通过这个参数最全的构造方法来看下线程池参数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
线程池任务提交过程:
任务提交的顺序为 corePoolSize –> workQueue –> maximumPoolSize -> handler。
线程池中的线程执行完当前任务后,会循环到任务队列中取任务继续执行;线程获取队列中任务时会阻塞,直到获取到任务返回;当线程数大于corePoolSize且线程阻塞时间超时,线程就会被销毁。
介绍四种创建线程池的方式:通过 ThreadPoolExecutor 的方式创建线程池及Executors工具类提供的三种创建方式。
直接调用 ThreadPoolExecutor 的构造方法,自己手动设置每一个参数,这是阿里推荐的方法。
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 ——《阿里巴巴Java开发手册》
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newSingleThreadExecutor与FixedThreadPool类似,不过是将线程数设置为1。
corePoolSize 和 maximumPoolSize都指定为1,表示该线程池中最多有一个线程,其他同FixedThreadPool。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
理解CachedThreadPool提交任务的过程:
CachedThreadPool的问题:如果主线程提交任务的速度远远大于CachedThreadPool的处理速度,则CachedThreadPool会不断地创建新线程来执行任务,这样有可能会导致系统耗尽CPU和内存资源,所以在使用该线程池时,一定要注意控制并发的任务数,否则创建大量的线程可能导致严重的性能问题。
【原创】01|开篇获奖感言 【原创】02|并发编程三大核心问题 【原创】03|重排序-可见性和有序性问题根源 【原创】04|Java 内存模型详解 【原创】05|深入理解 volatile 【原创】06|你不知道的 final 【原创】07|synchronized 原理 【原创】08|synchronized 锁优化 【原创】09|基础干货 【原创】10|线程状态 【原创】11|线程调度 【原创】12|揭秘 CAS 【原创】13|LockSupport 【原创】14|AQS 源码分析 【原创】15|重入锁 ReentrantLock 【原创】16|公平锁与非公平锁 【原创】17|读写锁八讲(上) 【原创】18|读写锁八讲(下) 【原创】19|JDK8新增锁StampedLock 【原创】20|StampedLock源码解析 【原创】21|Condition-Lock的等待通知 【原创】22|倒计时器CountDownLatch 【原创】22|倒计时器CountDownLatch 【原创】23|循环屏障CyclicBarrier 【原创】24|信号量Semaphore 【原创】25|交换器Exchangere 【原创】26|ConcurrentHashMap(上) 【原创】27|ConcurrentHashMap(下) 【原创】28|Copy-On-Write容器 【原创】29|ConcurrentLinkedQueue 【原创】30 | ThreadLocal 【原创】31 | 阻塞队列(上)