1. 线程池概念
概念: 一般情况下,一个请求需要至少开启一个线程来执行具体的请求内容,如果请求数量特别多,请求内容处理的时间非常短,则会造成频繁的创建和销毁线程,系统开销大。
- 线程池优势:
- 线程池可以维护多个线程的生命周期。
- 线程池可以通过线程复用来提高线程的利用率。
- 线程池可以提前创建好一批线程,在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟,这样,就可以立即为请求服务,使应用程序响应更快。
- 线程池中可以适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。
- 线程池缺点:
- 线程池本身也是需要维护的,当线程数量特别少,或者执行时间特别长的时候,不建议使用。
- 线程池容易遭受的并发风险,比同步错误,与池有关的死锁、资源不足和线程泄漏。
2. Callable
概念: Callable接口也是一个线程接口,内部配有一个带返回值的 call()
,实现了Callable接口的线程可以被异步提交到线程池中,并由线程池来异步非阻塞地执行 call()
,执行的结果会被线程池绑定到一个Future接口的实例中。
- Callable构造:
- Callable是一个函数式接口,可以使用匿名内部类或者lambda表示式来完成创建过程。
- 在声明Callable实例的时候需要指定一个泛型,这个泛型就是
call()
方法的返回值类型。
- Future常用方法:
V get()
: 获取线程中call()
方法的返回值,这个方法会阻塞,直到获取到结果。boolean cancel(boolean mayInterruptIfRunning)
: 取消线程任务。
- Callable VS Runnable:
- 实现Runnable接口,重写线程体方法
run()
:返回值为void。 - 实现Callable接口,重写线程体方法
call()
:返回值为接口泛型,会被放到一个Future中。
- 实现Runnable接口,重写线程体方法
源码: /javase-advanced/
- src:
c.y.thread.pool.CallableTest
/**
* @author yap
*/
public class CallableTest {
@SneakyThrows
@Test
public void callable() {
// can instead of lambda..
Callable<Integer> callable = () -> {
TimeUnit.SECONDS.sleep(2L);
return 100;
};
ExecutorService executorService = Executors.newCachedThreadPool();
// non-blocking
Future<Integer> future = executorService.submit(callable);
System.out.println("thread-main...");
System.out.println("thread-main...");
// blocking
System.out.println(future.get());
System.out.println("thread-main...");
System.out.println("thread-main...");
executorService.shutdown();
}
@SneakyThrows
@After
public void after() {
System.out.println(System.in.read());
}
}
3. FutureTask
概念: FutureTask类既实现了Runnable接口,又实现了Future接口,所以它既能作为一个线程任务,又能直接将线程任务的结果进行绑定存储。
源码: /javase-advanced/
- src:
c.y.thread.pool.FutureTaskTest
/**
* @author yap
*/
public class FutureTaskTest {
@SneakyThrows
@Test
public void futureTask() {
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
TimeUnit.SECONDS.sleep(2L);
return 100;
});
new Thread(futureTask).start();
System.out.println("thread-main...");
System.out.println("thread-main...");
// blocking
System.out.println(futureTask.get());
System.out.println("thread-main...");
System.out.println("thread-main...");
}
@SneakyThrows
@After
public void after() {
System.out.println(System.in.read());
}
}
4. CompletableFuture
概念: CompletableFuture类可以对多个带有返回值的任务进行统一的管理。
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
:- 异步执行一个带返回值的任务,并将任务结果进行存储。
static CompletableFuture<Void> runAsync(Runnable runnable)
:- 异步执行一个无回值的任务。
static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
:- 当所有指定的CompletableFuture全部都完成时,返回一个新的CompletableFuture。
static CompletableFuture<Void> anyOf(CompletableFuture<?>... cfs)
:- 当任意一个指定的CompletableFuture完成时,返回一个新的CompletableFuture。
T join()
:- 返回最终的结果。
源码: /javase-advanced/
- src:
c.y.thread.pool.CompletableFutureTest
/**
* @author yap
*/
public class CompletableFutureTest {
@SneakyThrows
private String taskA() {
TimeUnit.SECONDS.sleep(1L);
System.out.println("taskA over...");
return "taskA-over";
}
@SneakyThrows
private String taskB() {
TimeUnit.SECONDS.sleep(2L);
System.out.println("taskB over...");
return "taskB-over";
}
@SneakyThrows
private String taskC() {
TimeUnit.SECONDS.sleep(3L);
System.out.println("taskC over...");
return "taskC-over";
}
@SneakyThrows
private void taskD() {
TimeUnit.SECONDS.sleep(1L);
System.out.println("taskD over...");
}
@SneakyThrows
private void taskE() {
TimeUnit.SECONDS.sleep(2L);
System.out.println("taskE over...");
}
@SneakyThrows
private void taskF() {
TimeUnit.SECONDS.sleep(3L);
System.out.println("taskF over...");
}
@SneakyThrows
@Test
public void performTask() {
long start = System.currentTimeMillis();
taskA();
taskB();
taskC();
taskD();
taskE();
taskF();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
@SneakyThrows
@Test
public void performTaskByCompletableFuture() {
long start = System.currentTimeMillis();
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(this::taskA);
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(this::taskB);
CompletableFuture<String> futureC = CompletableFuture.supplyAsync(this::taskC);
CompletableFuture<Void> futureD = CompletableFuture.runAsync(this::taskD);
CompletableFuture<Void> futureE = CompletableFuture.runAsync(this::taskE);
CompletableFuture<Void> futureF = CompletableFuture.runAsync(this::taskF);
CompletableFuture<Void> future = CompletableFuture.allOf(futureA, futureB, futureC, futureD, futureE, futureF);
future.join();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
@SneakyThrows
@After
public void after() {
System.out.println(System.in.read());
}
}
5. ThreadPoolExecutor
概念: 线程池中维护了一个HashSet结构的线程集合和一个任务队列。
- 构造:
ThreadPoolExecutor()
,共有七个参数:corePoolSize
: 核心线程数,线程池维护的最少线程数量,即使空闲也不会给归还给OS。maximumPoolSize
: 线程池维护的最多线程数量,即当核心线程不够了,最大能拓展到多少。keepAliveTime
: 非核心线程所允许的最大的空闲时间,某个线程的空闲时间如果超过此指定值,会被归还给OS。unit
: 最大的空闲时间单位。workQueue
: 线程池所使用的工作队列,BlockingQueue 类型。threadFactory
: 线程工厂,用于产生线程,可以自定义,默认使用DefaultThreadFactory。handler
: 线程池对拒绝任务采取的拒绝策略,RejectedExecutionHandler类型。
- 方法:
void execute(Runnable command)
:将Runnable接口实例提交到线程池。Future<T> submit(Callable<T> task)
:将Callable接口实例提交到线程池,并将返回值结果绑定给Future。void shutdown()
:不再新增线程,发出停止信号,等所有线程执行完毕,关闭线程池,节省资源。void shutdownNow()
:不再新增线程,立即关闭线程池,节省资源。
流程: 假设线程池设置核心线程数为2,最大线程数为4,工作队列为固定值2,最大存活10s:
- 线程池最开始创建的时候,任务队列是空的。
- 当调用了
execute()
添加任务时:- 当第1个任务来了,启动一个线程,为核心线程。
- 当第2个任务来了,启动一个线程,为核心线程,此时核心线程数量已达最大。
- 当第3个任务来了,任务进入队列阻塞。
- 当第4个任务来了,任务进入队列阻塞,此时任务队列满了。
- 当第5个任务来了,拓展一个新线程,处理这个任务。
- 当第6个任务来了,拓展一个新线程,处理这个任务,此时到达线程数最大值。
- 当第7个任务来了,执行拒绝策略。
- 当一个线程完成了任务时,它会从等待队列中取下一个任务来执行。
- 当一个线程无事可做超过10s,只要不是核心线程,就停用掉。
源码: /javase-advanced/
- src:
c.y.thread.pool.ThreadPoolExecutorTest
/**
* @author yap
*/
public class ThreadPoolExecutorTest {
@SneakyThrows
@Test
public void threadPoolExecutor() {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2, 4, 3, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()
);
threadPool.execute(() -> {
System.out.println("runnable...");
});
TimeUnit.SECONDS.sleep(2L);
Future<Integer> future = threadPool.submit(() -> {
System.out.println("callable...");
return 100;
});
System.out.println(future.get());
}
@SneakyThrows
@After
public void after() {
System.out.println(System.in.read());
}
}
6. 线程池拒绝策略
概念: 线程池拒绝策略指的是当线程数已达最大时,对新申请加入的线程的拒绝方式,内置有四种:
- AbortPolicy:直接抛出异常,阻止系统正常工作。
- DiscardPolicy:直接啥事都不干,直接把任务丢弃。
- DiscardOldestPolicy:丢弃最老的一个请求(任务队列里面的第一个),让新来的任务入队。
- CallerRunsPolicy:只要线程池没有关闭,该策略直接在调用者线程中,执行当前被丢弃的任务,即哪个线程调用了线程池的execute()方法,哪个线程执行新来的任务。
源码: /javase-advanced/
- src:
c.y.thread.pool.RejectionStrategyTest
/**
* @author yap
*/
public class RejectionStrategyTest {
private static class MyTask implements Runnable {
private int i;
private MyTask(int i) {
this.i = i;
}
@SneakyThrows
@Override
public void run() {
System.out.println(Thread.currentThread() + ": " + i);
}
@Override
public String toString() {
return "MyTask{" + "i=" + i + "}";
}
}
private void policy(RejectedExecutionHandler policy) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),
policy);
for (int i = 0, j = 8; i < j; i++) {
threadPool.execute(new MyTask(i));
}
System.out.println("queue:" + threadPool.getQueue());
threadPool.execute(new MyTask(100));
System.out.println("queue:" + threadPool.getQueue());
threadPool.shutdown();
}
@Test
public void abortPolicy() {
policy(new ThreadPoolExecutor.AbortPolicy());
}
@Test
public void discardPolicy() {
policy(new ThreadPoolExecutor.DiscardPolicy());
}
@Test
public void discardOldestPolicy() {
policy(new ThreadPoolExecutor.DiscardOldestPolicy());
}
@Test
public void callerRunsPolicy() {
policy(new ThreadPoolExecutor.CallerRunsPolicy());
}
@SneakyThrows
@After
public void after(){
System.out.println(System.in.read());
}
}
7. Executor框架
概念: 尽量避免使用Executor框架创建线程池,因为创建线程时用的内存并不是jvm堆内存,而是系统直接内存,而Executor大部分框架允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
7.1 CachedThreadPool
概念: CachedThreadPool是一个可缓存的线程池。
- 核心线程数为0,即所有线程都可以被回收。
- 最大线程数为Integer.MAX_VALUE,几乎接近于无限制,会导致OOM。
- 线程最大空闲时间为60s。
- 工作队列为
SynchronousQueue
同步队列,多用于手递手传递数据,几乎无队列等待现象,适用于处理大量耗时短的任务。 - 构造:
Executors.newCachedThreadPool()
:
源码: /javase-advanced/
- src:
c.y.thread.pool.CachedThreadPoolTest
/**
* @author yap
*/
public class CachedThreadPoolTest {
@Test
public void cachedThreadPool() throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0, j = 10; i < j; i++) {
TimeUnit.SECONDS.sleep(1);
executorService.execute(() -> {
System.out.println("hello!");
});
}
}
@SneakyThrows
@After
public void after() {
System.out.println(System.in.read());
}
}
7.2 FixedThreadPool
概念: FixedThreadPool是一个线程数固定的,无序的线程池。
- 核心线程数需要在构造的时候指定,一旦指定不可更改。
- 最大线程数和核心线程数一致,即所有线程都是核心线程。
- 线程最大空闲时间为0s,空闲时间对于核心线程来说是被忽略的。
- 工作队列为
LinkedBlockingQueue
无界阻塞队列,容易OOM。 - 构造:
Executors.newFixedThreadPool(2)
:
源码: /javase-advanced/
- src:
c.y.thread.pool.FixedThreadPoolTest
/**
* @author yap
*/
public class FixedThreadPoolTest {
@Test
public void cachedThreadPool() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0, j = 10; i < j; i++) {
TimeUnit.SECONDS.sleep(1);
executorService.execute(() -> {
System.out.println("hello!");
});
}
}
@SneakyThrows
@After
public void after() {
System.out.println(System.in.read());
}
}
7.3 ScheduledThreadPool
概念: ScheduledThreadPool是一个周期线程池,可以延时或者周期地执行任务。
- 构造:
Executors.newScheduledThreadPool(1)
: - 方法:
schedule()
:延迟多久后执行任务。- param1: 执行任务的Callable或Runnable接口实例。
- param2: 延时执行任务的延迟时间。
- param3: 时间单位。
ScheduledFuture<?> scheduleAtFixedRate()
:- param1: 执行任务的Callable或Runnable接口实例。
- param2: 第一次执行任务的延迟时间。
- param3: 连续执行周期任务的时间间隔,从上一个任务开始执行时始计算。
- param4: 时间单位。
ScheduledFuture<?> scheduleWithFixedDelay()
:- param1: 执行任务的Callable或Runnable接口实例。
- param2: 第一次执行任务的延迟时间。
- param3: 连续执行周期任务的时间间隔,从上一个任务执行结束时计算。
- param4: 时间单位。
源码: /javase-advanced/
- src:
c.y.thread.pool.ScheduledThreadPoolTest
/**
* @author yap
*/
public class ScheduledThreadPoolTest {
@Test
public void schedule() {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
executorService.schedule(() -> {
System.out.println("delay 2s and print");
}, 2, TimeUnit.SECONDS);
}
@Test
public void scheduleAtFixedRate() {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
executorService.scheduleAtFixedRate(() -> {
System.out.println("delay 2s and every 1s");
}, 2,1, TimeUnit.SECONDS);
}
@Test
public void scheduleWithFixedDelay() {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
executorService.scheduleWithFixedDelay(() -> {
System.out.println("delay 2s and every 1s");
}, 2,1, TimeUnit.SECONDS);
}
@SneakyThrows
@After
public void after() {
System.out.println(System.in.read());
}
}
7.4 SingleThreadPool
概念: SingleThreadExecutor是只拥有唯一线程来执行任务的线程池。
- 核心线程数为1,最大线程数为1,即线程池中只有一个核心线程。
- 线程最大空闲时间为0s,空闲时间对于核心线程来说是被忽略的。
- 工作队列为
LinkedBlockingQueue
无界阻塞队列,容易OOM。 - 构造:
Executors.newSingleThreadExecutor()
: - SingleThreadExecutor一般用户保证所有任务按照指定的顺序执行。
- 这个唯一的线程不能被更改。
- 构造:
Executors.newSingleThreadExecutor()
:
源码: /javase-advanced/
- src:
c.y.thread.pool.SingleThreadPoolTest
/**
* @author yap
*/
public class SingleThreadPoolTest {
@Test
public void cachedThreadPool() throws InterruptedException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0, j = 10; i < j; i++) {
TimeUnit.SECONDS.sleep(1);
executorService.execute(() -> {
System.out.println("hello!");
});
}
}
@SneakyThrows
@After
public void after() {
System.out.println(System.in.read());
}
}
8. 自定义线程池
概念: Executors中提供的线程池框架或多或少都有一些局限性和不足,我们工作中通常自定义线程池的参数来灵活控制线程池的特性:
- 自定义线程池工厂:
- 实现ThreadFactory接口。
- 重写
Thread newThread(Runnable r)
。 - 创建可以自定义命名的线程。
- 自定义拒绝策略:
- 实现RejectedExecutionHandler接口。
- 重写
void rejectedExecution(Runnable r, ThreadPoolExecutor e)
。 - 将未能处理的任务保存到redis或者数据库中,在其他时间进行处理。
源码: /javase-advanced/
- src:
c.y.thread.pool.CustomThreadPoolTest
/**
* @author yap
*/
public class CustomThreadPoolTest {
private static class CustomThreadFactory implements ThreadFactory {
private final AtomicInteger threadId = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "myThread-" + threadId.getAndIncrement());
}
}
private static class CustomRejectedPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
System.out.println(((CustomTask) r).getTaskName() + " is rejected!");
}
}
@AllArgsConstructor
@Data
private static class CustomTask implements Runnable {
private String taskName;
@SneakyThrows
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": " + taskName + " is running!");
TimeUnit.SECONDS.sleep(3L);
}
}
@Test
@SneakyThrows
public void customThreadPool() {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2, 4, 10, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
new CustomThreadFactory(),
new CustomRejectedPolicy());
for (int i = 0, j = 8; i < j; i++) {
threadPool.execute(new CustomTask("myTask-" + i));
}
}
@SneakyThrows
@After
public void after() {
System.out.println(System.in.read());
}
}