为什么要用线程池
线程复用:通过创建线程池可以使一个线程能执行多次任务,使线程重复利用,减少了创建和销毁线程的消耗。
线程管理:通过线程池,我们可以控制最大线程数量,指定线程数饱和后的处理方式,以及等待时间的控制。
线程池核心参数
核心线程数:线程池中固定的存活线程数,一般情况下这类线程不会被销毁,不过可以通过以下设置来实现核心线程的销毁,也就是当核心线程空闲的时间超过了设置的空闲线程存活时间就会被销毁。
executor.allowCoreThreadTimeOut(true);
最大线程数:当线程池中的核心线程不足于处理提交的任务,线程池就会创建一些新的临时线程来处理任务,不过新增的线程不会超过最大线程数。
空闲线程存活时间:当新创建的线程完成了任务后,空闲超过此时间,线程池就会将其销毁。
时间单位:空闲线程存活时间的时间单位,通常是TimeUnit.SECONDS
或TimeUnit.MILLISECONDS
任务队列:当任务提交数量大于最大线程数,多出来的任务就会进入阻塞队列中。
线程工厂:用于创建线程,也可以自定义一个线程工厂,为创建的线程取一个名字:
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class NamedThreadFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public NamedThreadFactory(String namePrefix) {
this.namePrefix = namePrefix;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());
return t;
}
}
拒绝策略:当任务数量已经超过了有容量限制的任务队列的容量,就会触发拒绝策略来处理这些多出来的任务。
线程池种类
以下四种是最常见的线程池:
newFixedThreadPool
- 线程数固定,当任务数量超过线程数会将其提交到阻塞队列,这里的阻塞队列是一个无界的阻塞队列,使用LinkedBlockingQueue来实现。
- 适用于处理大量短时间任务
newCachedThreadPool
- 线程数不固定,会随任务数量的增加而增加,其阻塞队列(SynchronousQueue)不存储任务,只是用于中转任务,类似于生产者消费者模式, 当有线程处理继续添加任务,当无线程处理会阻塞生产者线程提交任务。
-适用于短期大量异步任务
newSingleThreadExecutor
-线程池中只有一个线程,其阻塞队列使用 LinkedBlockingQueue和固定线程池一样的无界队列。
-适用于对顺序处理任务的场景
newScheduledThreadPool
-支持定期重复执行任务和延时处理任务,使用的阻塞队列是DelayedWorkQueue,这个队列基于优先队列将任务按照延迟时间来排序。
-适用于定时任务
对其他线程池或者线程池操作感兴趣可以查阅中文文档:Java17中文文档 - API参考文档 - 全栈行动派 (qzxdp.cn)
线程池的拒绝策略
AbortPolicy:
- 策略:
- 抛出
RejectedExecutionException
异常。- 适用场景:
- 当程序出现错误或资源不足时,希望立即失败并通知调用方。
- 不希望丢失任务或让线程池继续运行,而是希望程序能够尽快终止。
CallerRunsPolicy:
- 策略:
- 调用者所在的线程会执行该任务,不会将其放入线程池。
- 适用场景:
- 当线程池暂时繁忙但希望确保所有任务都能被执行时。
- 适用于任务执行时间较短的场景,因为这可能会增加主线程的负担。
DiscardPolicy:
- 策略:丢弃任务,不执行也不抛出异常。
- 适用场景:
- 当任务的重要性较低,可以接受丢失某些任务。
- 适用于任务可以重试的场景,或者任务的丢失对系统影响不大。
DiscardOldestPolicy:
- 策略:
- 丢弃队列中最老的任务,并尝试重新提交新任务到线程池。
- 适用场景:
- 当希望尽可能执行最新任务时。
- 适用于需要处理最新数据或命令的场景,例如实时更新或最近的用户输入。
线程池实现线程复用的原理
在线程池中每个线程被包装成了worker对象,然后每个worker对象中有一个Thread对象
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
@SuppressWarnings("serial") // Unlikely to be serializable
final Thread thread;
/** Initial task to run. Possibly null. */
@SuppressWarnings("serial") // Not statically typed as Serializable
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
// TODO: switch to AbstractQueuedLongSynchronizer and move
// completedTasks into the lock word.
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker. */
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
可以看到Worker里有一个runWorker方法,其内部核心代码如下:
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
try {
task.run();
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
可以看到该线程是在一个while循环中,如果getTask不为空或者当前任务不为空就一直执行下去,
下面是getTask的源码:
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
// Check if queue empty only if necessary.
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
可以看到其中有一个死循环,死循环中有两个判断返回null的条件,一个就是线程池正在关闭而且任务队列为空,另一个要分成以下四种情况:
1.当前线程大于最大线程数且任务队列为空
2.核心线程允许超时且已经超时和任务队列为空或者线程数大于1
线程池操作
向线程池提交任务:
1. 使用 execute()
execute()
方法用于执行一个 Runnable
任务,它不返回任何值。使用 execute()
方法时,你只需提供一个 Runnable
实例。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务
for (int i = 0; i < 15; i++) {
final int index = i;
executor.execute(() -> {
System.out.println("Task " + index + " is running by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 关闭线程池
executor.shutdown();
}
}
2. 使用 submit()
submit()
方法用于执行一个 Callable
或 Runnable
任务,并返回一个 Future
对象。Future
对象允许你将来获取任务的结果或取消任务的执行。如果任务的结果不是必需的,也可以传入一个 Runnable
。
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务
for (int i = 0; i < 15; i++) {
final int index = i;
Future<?> future = executor.submit(() -> {
System.out.println("Task " + index + " is running by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 可选操作:取消任务或获取结果
// future.cancel(true); // 取消任务
// future.get(); // 获取任务结果
}
// 关闭线程池
executor.shutdown();
}
}
关闭线程池
1. 使用 shutdown()
shutdown()
方法会平滑地关闭线程池,即不再接受新的任务提交,但会等待已提交的任务执行完毕后再关闭线程池。
- 线程池不会立即关闭。
- 已经提交的任务将继续执行直到完成。
- 新的任务将不再被接受。
- 该方法返回
void
,不会立即返回结果。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务
for (int i = 0; i < 15; i++) {
final int index = i;
executor.execute(() -> {
System.out.println("Task " + index + " is running by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 关闭线程池
executor.shutdown();
// 等待所有任务完成
while (!executor.isTerminated()) {
// 等待线程池关闭
}
System.out.println("All tasks completed.");
}
}
2. 使用 shutdownNow()
shutdownNow()
方法也会停止接受新的任务提交,并试图停止正在执行的任务,同时返回一个包含尚未执行的任务列表。
- 线程池会立即停止接受新任务。
- 正在执行的任务将被尝试中断。
- 未执行的任务将被取消并返回。
- 该方法返回一个包含未执行的任务列表。
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务
for (int i = 0; i < 15; i++) {
final int index = i;
executor.execute(() -> {
System.out.println("Task " + index + " is running by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 关闭线程池
List<Runnable> notRunTasks = executor.shutdownNow();
// 等待所有任务完成
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("Some tasks did not complete.");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("All tasks completed or interrupted.");
}
}
注意事项
等待线程池完全关闭:
- 使用
shutdown()
后,你可以使用isTerminated()
方法来检查线程池是否已完成所有任务。- 使用
shutdownNow()
后,你可以使用awaitTermination()
方法来等待线程池关闭。任务中断:
- 使用
shutdownNow()
可能会导致正在执行的任务被中断。如果任务中没有适当处理中断信号,可能会导致资源泄露或不一致的状态。取消任务:
- 如果你使用
submit()
方法提交任务并获得了Future
对象,你可以调用future.cancel(true)
来尝试取消任务。资源清理:
- 如果线程池中的任务需要进行资源清理,确保在任务结束时执行清理操作,或者使用
try-with-resources
语句来自动关闭资源