一、线程池介绍
概念
线程池,本质上是一种对象池,用于管理线程资源。
在任务执行之前,需要从线程池中拿出线程来执行。
在任务执行完成之后,需要把线程放回线程池。
通过线程的这种反复机制,可以有效地避免直接创建线程所带来的坏处。
池化技术
为了减少对对象创建和销毁时造成的消耗,提前在某个容器里创建大量的对象资源,需要使用时从容器内获取对象,使用完毕时再将对象还回容器中以便重复使用。
该技术就是池化技术,能提高程序的性能,在高并发条件下效果更加明显,比较典型的池化技术有线程池、连接池、内存池和对象池等。
单线程的缺点
- 频繁的线程创建和销毁会占用更多的CPU和内存。
- 频繁的线程创建和销毁会对GC产生比较大的压力。
- 线程太多,线程切换带来的开销将不可忽视。
- 线程太少,多核CPU得不到充分利用,是一种浪费。
线程池的优点
- 降低资源的消耗。线程本身是一种资源,创建和销毁线程会有CPU开销;创建的线程也会占用一定的内存。
- 提高任务执行的响应速度。任务执行时,可以不必等到线程创建完之后再执行。
- 提高线程的可管理性。线程不能无限制地创建,需要进行统一的分配、调优和监控。
线程池流程
-
创建完线程池后,开始等待任务请求。
-
当线程池执行execute()方法时,将任务进行如下判断:
- 判断核心线程数是否已满,未满执行该任务。
- 判断阻塞队列是否已满,未满将任务放入阻塞队列中。
- 判断线程池是否已满,未满由临时线程执行任务。
- 线程池已满,执行拒绝策略。
-
当线程完成任务时,会从阻塞队列中取下一个任务来执行。
-
当线程无事可做时,线程池会进行如下判断:
- 空闲时间是否超过空闲时间,未超过继续等待。
- 当前线程总数是否大于核心线程数,未超过继续等待。
- 已超过则停掉临时线程,直至到核心线程数为止。
二、线程池创建
1.newFixedThreadPool
一个任务一个任务执行的场景。
创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)。
源码
/**
* Creates an Executor that uses a single worker thread operating
* off an unbounded queue. (Note however that if this single
* thread terminates due to a failure during execution prior to
* shutdown, a new one will take its place if needed to execute
* subsequent tasks.) Tasks are guaranteed to execute
* sequentially, and no more than one task will be active at any
* given time. Unlike the otherwise equivalent
* {@code newFixedThreadPool(1)} the returned executor is
* guaranteed not to be reconfigurable to use additional threads.
* ----------------------------------------------------------------
* 创建一个执行器,该执行器使用单个工作线程操作一个未绑定队列。
* (但是请注意,如果这个线程在关闭之前由于执行失败而终止,
* 那么在需要执行后续任务时,一个新的线程将取代它。)
* 任务保证按顺序执行,并且在任何给定时间内活动的任务不超过一个。
* 与等效的{@code newFixedThreadPool(1)}不同,
* 返回的执行器保证不会被重新配置以使用额外的线程。
* ---------------------------------------------------------------
* @return the newly created single-threaded Executor
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
2.newFixedThreadPool
执行长期的任务,性能好很多。
创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)。
源码
/**
* Creates a thread pool that reuses a fixed number of threads
* operating off a shared unbounded queue. At any point, at most
* {@code nThreads} threads will be active processing tasks.
* If additional tasks are submitted when all threads are active,
* they will wait in the queue until a thread is available.
* If any thread terminates due to a failure during execution
* prior to shutdown, a new one will take its place if needed to
* execute subsequent tasks. The threads in the pool will exist
* until it is explicitly {@link ExecutorService#shutdown shutdown}.
* ------------------------------------------------------------------
* 创建一个线程池,该线程池重用固定数量的线程在一个共享的无边界队列上操作。
* 在任何时候,最多{@code nThreads}的线程将是活动的处理任务。
* 如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待,
* 直到有一个线程可用为止。如果任何线程在关闭前的执行过程中由于失败而终止,
* 那么在需要执行后续任务时,将有一个新的线程替代它。
* 池中的线程将一直存在,直到显式地为{@link ExecutorService#shutdown shutdown}。
* ---------------------------------------------------------------------
* @param nThreads the number of threads in the pool
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
3.newScheduledThreadPool
周期性执行任务的场景。
创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构。
源码
/**
* Creates a thread pool that can schedule commands to run after a
* given delay, or to execute periodically.
* @param corePoolSize the number of threads to keep in the pool,
* even if they are idle
* ----------------------------------------------------------------
* 创建一个线程池,它可以调度命令在给定的延迟后运行或定期执行。
* @param corePoolSize保留在池中的线程数,即使它们是空闲的。
* ----------------------------------------------------------------
* @return a newly created scheduled thread pool
* @throws IllegalArgumentException if {@code corePoolSize < 0}
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
/**
* Creates a new {@code ScheduledThreadPoolExecutor} with the
* given core pool size.
* --------------------------------------------------------------------
* 使用给定的核心池大小创建一个新的{@code ScheduledThreadPoolExecutor}。
* --------------------------------------------------------------------
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @throws IllegalArgumentException if {@code corePoolSize < 0}
*/
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
4.newCachedThreadPool
执行很多短期异步的小程序或者负载较轻的服务器。
当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
源码
/**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available, and uses the provided
* ThreadFactory to create new threads when needed.
* -----------------------------------------------------------------
* 创建一个线程池,该线程池根据需要创建新线程,但在可用时将重用之前构建的线程,
* 并在需要时使用提供的ThreadFactory创建新线程。
* ----------------------------------------------------------------
* @param threadFactory the factory to use when creating new threads
* @return the newly created thread pool
* @throws NullPointerException if threadFactory is null
*/
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
* 注意事项
通常情况下均不使用上述的方法创建,原因如《阿里巴巴Java开发手册》所述,因为默认使用了LinkedBlockQueue和SynchronizedQueue,都是无边界的阻塞队列,最大长度为Integer.MAX_VALUE。因为可以不断地向队列中加入任务,所以会导致OOM。
所以通常是自己定义创建线程池来使用,如下:
ExecutorService threadPool = new ThreadPoolExecutor(
3,
5,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
三、线程池的七个参数
1.corePoolSize
核心线程数,线程池中的常驻核心线程数。
2.maximumPoolSize
最大线程数,线程池中能容纳同时执行的最大线程数(maximumPoolSize≥1)。
3.keepAliveTime
存活时间,多余的空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁,知道剩corePoolSize个线程为止。
4.unit
存活时间单位,keepAliveTime的单位,在java.util.concurrent.TimeUnit类里取值。
5.workQueue
阻塞队列,被提交但尚未被执行的任务。当线程池中的线程数超过corePoolSize时,任务将放在阻塞队列里,它是一个BlockingQueue类型的对象。
- ArrayBlockingQueue,队列是有界的,基于数字实现的阻塞队列。
- LinkedBlockingQueue,队列可以有界,也可以无界。基于链表实现的阻塞队列。
- SynchronousQueue,不存储元素的阻塞队列,内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。
- PriorityBlockingQueue,带优先级的无界阻塞队列。
6.threadFactoty
线程工厂,用于创建线程,一般使用默认Executors.defaultThreadFactory()。
/**
* The default thread factory
* 创建一个默认线程,名称为pool-{poolNum}-thread-{threadNum}
*/
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
7.handler
拒绝策略,当任务队列满了并且任务线程大于等于线程池的maximumPoolSize时,使用拒绝策略来进行回调处理。
四、线程池的拒绝策略
当阻塞队列和线程都满了时, 线程池会对后续进来的任务进行处理:抛异常提醒、直接丢弃、与旧任务替换或者返回调用者。
1.AbortPolicy
ThreadPoolExecutor.AbortPolicy
这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出RejectedExecutionException异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。
2.DiscardPolicy
ThreadPoolExecutor.DiscardPolicy
使用此策略,直接丢弃该任务,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。例如,本人的博客网站统计阅读量就是采用的这种拒绝策略。
3.DiscardOldestPolicy
ThreadPoolExecutor.DiscardOldestPolicy
此拒绝策略,是一种喜新厌旧的拒绝策略,丢弃阻塞队列中最老的任务,在添加新任务。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。
4.CallerRunsPolicy
ThreadPoolExecutor.CallerRunsPolicy
如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大。
五、线程池方法
1.execute()
接口Executor的方法,用于提交不需要返回结果的任务。
void execute(Runnable command);
package org.example.thread;
import java.util.concurrent.*;
public class test {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
3, 5, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
threadPool.execute(()->{
System.out.println("hello word!");
});
}
}
2.submit()
接口ExecutorService的方法,用于提交需要返回结果的任务。
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
该方法会返回Future对象,通过使用该对象的get()方法就可以获得返回结果,但是get()方法会一直阻塞直至结果的返回。
package org.example.thread;
import java.util.concurrent.*;
public class test {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
ExecutorService threadPool = new ThreadPoolExecutor(
3,
5,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Future<String> submit = threadPool.submit(() -> {
return "hello word";
});
System.out.println(submit.get());
// System.out.println(submit.get(1L, TimeUnit.SECONDS));
}
}
3.shutdown()
将线程池状态置为SHUTDOWN,不再接受新的任务,同时会等待线程池中已有的任务执行完成再结束。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
4.shutdownNow()
会将线程池状态置为SHUTDOWN,对所有线程执行interrupt()操作,清空队列,并将队列中的任务返回回来。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
关闭案例
package org.example.thread;
import java.util.concurrent.*;
public class test {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
3, 5, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
Future<String> submit = threadPool.submit(() -> {
return "hello word";
});
try {
System.out.println(submit.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
if (threadPool != null) {
threadPool.shutdown();
}
}
}
}
六、线程池的参数配置
1.CPU密集型
CPU使用率较高时,CPU资源消耗就大。所以CPU密集型适合运算,比如计算圆周率或对高清视频进行解码等。如果任务多,并且花在任务切换时间多,CPU执行任务的效率就低下,不适合使用CPU密集型。
计算方法 = CPU核数 + 1
2.I/O密集型
I/O密集型适合读写,当CPU使用率较低时,程序中的I/O操作会占用大量的时间,比如对数据库的读写操作等,消耗CPU的资源很少,任务的大部分时间都在在等待IO操作。
计算方法1 = CPU核数 * 2
计算方法2 = CPU ( 1 - 阻塞系数 ) 【阻塞系数在0.8~0.9之间】
* 查询CUP核数方法
- Java代码
package org.example.thread;
public class test {
public static void main(String[] args) {
System.out.println("cpu核心数:" + Runtime.getRuntime().availableProcessors());
}
}
- 任务管理器
- 计算机管理-处理器