ThreadPool
写在前边
一直对线程池的理解就是下边的代码。之后看到别人发的文章,线程池的坑,才发现原来线程池也有这么多的坑。所以就想着好好的学习一下线程池。
ExecutorService executorService = Executors.newFixedThreadPool(10);
自定义简单线程池
线程池的大概结构,可以看到主要有两部分线程池、队列。

阻塞队列
因为在线程池中要用到队列,所以先写队列的代码
基本属性:阻塞队列、锁、队列的容量、队列满的时候的状态量、队列空的时候的状态量
// 1.定义阻塞队列
private Deque<T> queue = new ArrayDeque<>();
// 2.因为是高并发,所以要有锁
private ReentrantLock lock = new ReentrantLock();
// 3.定义一个容量的大小
private int capacity;
// 4.定义一个队列满的时候的状态量,表示,不能向队列中放元素了
private Condition fullWaitState = lock.newCondition();
// 5.定义一个队列空的状态量,表示,线程不能再消费了
private Condition emptyWaitState = lock.newCondition();
构造方法
public BlockingQueue(int queueCapacity) {
this.capacity = queueCapacity;
}
三个方法:获取任务、放入任务、队列的最大容量。
放入任务(死等)
因为阻塞队列不是线程安全的,所以在放入任务的时候要加锁。
队列可能是满了,所以要判断当前的队列容量。如果已经满了,那么就要阻塞这个线程。如果队列没有满,那么就调用对列的相关方法将任务放入队列中。
在一个新的任务放入队列之前,有可能有其他线程因为队列空了,陷入了等待状态,所以在放入任务之后,要唤醒从队列中拿任务的线程。
最后释放锁。
public void put(T element){
lock.lock();
try {
// 是不是满了
while (queue.size() == capacity) {
// 满了,阻塞
try {
fullWaitState.await();
log.info("正在等待添加到任务队列...,{}", element);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不满了,就能放进去了
queue.addLast(element);
log.info("放入队列,任务:{}", element);
// 如果现在又因为队列是空的而阻塞的线程,就唤醒
emptyWaitState.signal();
} finally {
lock.unlock();
}
}
但是这种方式有不好的一点,如果执行一个任务的时间特别长,并且队列已经满了,那么,因为fullWaitState.await();这个代码,线程会死等直到线程执行完毕。
放入任务(带超时时间阻塞添加)
代码和上边的一段代码基本一样。nacos = fullWaitState.awaitNanos(nacos);会返回当前还需要等待的时间。
public boolean poll(T element, long timeout, TimeUnit unit) {
lock.lock();
long nacos = unit.toNanos(timeout);
try {
// 是不是满了
while (queue.size() == capacity) {
// 满了,阻塞
try {
if (nacos <= 0) {
return false;
}
nacos = fullWaitState.awaitNanos(nacos);
log.info("正在等待添加到任务队列...,{}", element);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不满了,就能放进去了
queue.addLast(element);
log.info("放入队列,任务:{}", element);
// 如果现在又因为队列是空的而阻塞的线程,就唤醒
emptyWaitState.signal();
return true;
} finally {
lock.unlock();
}
}
获取任务(阻塞获取)
可能有多个线程同时获取任务,加锁。
队列可能是空的,空的话,还取什么任务呀,要判断。
是空的,就等待,阻塞等待。
不是空的,就从队列的头拿到元素。
在获得任务的时候或者之前,已经有任务因为队列满了,所以线程被阻塞了,所以要唤醒那写阻塞的线程,让他们向队列里放任务。
最后,释放锁。
public T take() {
// 加锁取
lock.lock();
try {
// 队列不能是空
while (queue.isEmpty()) {
// 阻塞
try {
emptyWaitState.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不空,就能拿元素
T t = queue.removeFirst();
// 有位置了,可以往里放元素了
fullWaitState.signal();
return t;
} finally {
lock.unlock();
}
}
获取任务(带超时时间)
如果获取时间已经大于了最大能等的时间,返回空,表示没有拿到任务。
// 带超时时间的获取
public T poll(long timeout, TimeUnit unit) {
// 加锁取
lock.lock();
// 将时间转化成为统一的时间
long nanos = unit.toNanos(timeout);
try {
// 队列不能是空
while (queue.isEmpty()) {
// 阻塞
try {
if (nanos <= 0) {
return null;
}
// 返回值是剩下的等待时间
nanos = emptyWaitState.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不空,就能拿元素
T t = queue.removeFirst();
// 有位置了,可以往里放元素了
fullWaitState.signal();
return t;
} finally {
lock.unlock();
}
}
获取队列最大容量
public int size() {
return capacity;
}
线程池
线程池基本属性:阻塞队列、线程集合、核心线程数、超时时间、拒绝策略
拒绝策略:就是当线程池中的所有线程都被占用且阻塞队列也满了之后,线程池怎么对待后续想要添加进来的任务。这里将拒绝策略下放给了用户,让用户自定义
// 要有阻塞队列
private BlockingQueue<Runnable> taskQueue;
// 线程集合
private HashSet<Worker> workers = new HashSet();
// 核心线程数
private int coreSize;
// 线程的超时时间
private long timeout;
// 时间单位
private TimeUnit timeUnit;
// 拒绝策略
private RejectPolicy rejectPolicy;
拒绝策略
函数式接口
@FunctionalInterface
interface RejectPolicy<T> {
void reject(BlockingQueue<T> queue, T task);
}
构造方法
taskQueue创建线程池的时候就要有,不能让用户自己传。
线程集合也要自己定义
其他的都让用户自己传
// 构造方法,线程数,超时时间是自定义的
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity, RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
// 要有一个容量
this.taskQueue = new BlockingQueue<>(queueCapacity);
this.rejectPolicy = rejectPolicy;
}
线程集合
内部类。
要让Woker继承Thread这样才能创建线程
线程要执行任务,所以要有Runnable的实例化对象,且通过构造函数传进来。
重写run方法,这样,让Worker的实例化对象调用start()方法的时候,就会自动执行类里边的run方法。
只有传进来的任务不是空的话,才执行代码task != null
线程池时可以复用的,所以,不能因为传进来的task是空的就终止线程了
还要看阻塞队列中是否还有任务。(task = taskQueue.poll(timeout, timeUnit)) != null)
如果有的话,就执行任务。
如果没有,就移除这个worker即可。
class Worker extends Thread {
private Runnable task;
Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
// 因为是线程池,所以不能线程结束了,就立即终止线程
while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
// 执行任务
try {
log.info("正在执行...,{}", task);
task.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
task = null;
}
}
// 退出集合,说明这个线程已经执行完所有的方法了,就要移除掉
synchronized (workers) {
log.info("worker被移除,{}", this);
workers.remove(this);
}
}
}
线程池执行方法
线程池开始执行实际上是在内部生成Woker对象来执行的。
加锁,避免创建过多的线程。
判断,如果当前线程集合中的线程小于核心线程数的话。就可以为当前任务分配新的线程Worker worker = new Worker(task);
将线程放入线程集合中workers.add(worker);
执行worker.start();
public void execute(Runnable task) {
// Workers是线程不安全的,所以要加锁
// 传进来task,交给一个线程来执行
// 要判断能不能放进到works
synchronized (workers) {
if (workers.size() < coreSize) {
// 说明和兴线程数还是用空余的,可以执行
Worker worker = new Worker(task);
log.info("Worker对象:{},任务:{}", worker, task);
// 放入线程集合中
workers.add(worker);
// 执行任务
worker.start();
} else {
// 说明核心线程已经用完了,将任务放到阻塞队列中
// taskQueue.put(task);
// 1 队列满,死等
// 2 带超时等待
// 3 让调用者放弃任务
// 4 让调用者抛出异常
// 5 让调用者自己执行任务
taskQueue.tryPut(task, rejectPolicy);
}
}
}
调用执行策略
如果核心线程都在忙碌的情况下,就将任务放入阻塞队列中。
加锁
判断阻塞队列的大小是否已经超过设定的最大值了
没有的话,就将任务放入队列中。
已经超过的话,就要执行决绝策略了。
public void tryPut(T task, RejectPolicy<T> rejectPolicy) {
lock.lock();
try {
if (queue.size() < capacity) {
queue.addLast(task);
log.info("放入队列,任务:{}", task);
// 如果现在又因为队列是空的而阻塞的线程,就唤醒
emptyWaitState.signal();
} else {
rejectPolicy.reject(this, task);
}
} finally {
lock.unlock();
}
}
拒绝策略的可能策略
// 1 队列满,死等
// taskQueue.put(task);
// 2 带超时等待
// taskQueue.poll(task, 1500, TimeUnit.MILLISECONDS);
// 3 让调用者放弃任务
// log.debug("放弃,{}", task);
// 4 让调用者抛出异常
// throw new RuntimeException("任务执行失败");
// 5 让调用者自己执行任务
// task.run();
入口函数
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1, (taskQueue, task)->{
// 1 队列满,死等
// taskQueue.put(task);
// 2 带超时等待
// taskQueue.poll(task, 1500, TimeUnit.MILLISECONDS);
// 3 让调用者放弃任务
// log.debug("放弃,{}", task);
// 4 让调用者抛出异常
// throw new RuntimeException("任务执行失败");
// 5 让调用者自己执行任务
// task.run();
});
for (int i = 0; i < 3; i++) {
int j = i;
threadPool.execute(() -> {
try {
TimeUnit.SECONDS.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("llllllllll===,{}", j);
});
}
}
测试:
死等
一个线程,阻塞队列容量是1,所以,一个任务放入了Woker中,另外一个放入了阻塞队列中。最后一个任务就只能死等。

超时等待更改TimeUnit.SECONDS.sleep(1);可以等1.5秒,线程最多执行的时间是1秒,所以,都可以执行完

主动放弃,什么也不执行。更改TimeUnit.*SECONDS*.sleep(2);会执行完0,1。2不会输出

让调用者抛出异常

调用者自己执行任务,因为队列满了之后就要执行决绝策略,就会直接执行代码。

完整代码
/**
* @author HGL
* @time 2023-05-27
*/
@Slf4j
public class ThreadPoolTest01 {
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1, (taskQueue, task)->{
// 1 队列满,死等
// taskQueue.put(task);
// 2 带超时等待
// taskQueue.poll(task, 1500, TimeUnit.MILLISECONDS);
// 3 让调用者放弃任务
// log.info("放弃,{}", task);
// 4 让调用者抛出异常
// throw new RuntimeException("任务执行失败");
// 5 让调用者自己执行任务
task.run();
});
for (int i = 0; i < 3; i++) {
int j = i;
threadPool.execute(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("llllllllll===,{}", j);
});
}
}
}
@FunctionalInterface
interface RejectPolicy<T> {
void reject(BlockingQueue<T> queue, T task);
}
@Slf4j(topic = "threadpool")
class ThreadPool {
// 要有阻塞队列
private BlockingQueue<Runnable> taskQueue;
// 线程集合
private HashSet<Worker> workers = new HashSet();
// 核心线程数
private int coreSize;
// 线程的超时时间
private long timeout;
private TimeUnit timeUnit;
private RejectPolicy rejectPolicy;
// 构造方法,线程数,超时时间是自定义的
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity, RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
// 要有一个容量
this.taskQueue = new BlockingQueue<>(queueCapacity);
this.rejectPolicy = rejectPolicy;
}
// 执行方法
public void execute(Runnable task) {
// Workers是线程不安全的,所以要加锁
// 传进来task,交给一个线程来执行
// 要判断能不能放进到works
synchronized (workers) {
if (workers.size() < coreSize) {
// 说明和兴线程数还是用空余的,可以执行
Worker worker = new Worker(task);
log.info("Worker对象:{},任务:{}", worker, task);
// 放入线程集合中
workers.add(worker);
// 执行任务
worker.start();
} else {
// 说明核心线程已经用完了,将任务放到阻塞队列中
// taskQueue.put(task);
// 1 队列满,死等
// 2 带超时等待
// 3 让调用者放弃任务
// 4 让调用者抛出异常
// 5 让调用者自己执行任务
taskQueue.tryPut(task, rejectPolicy);
}
}
}
class Worker extends Thread {
private Runnable task;
Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
// 因为是线程池,所以不能线程结束了,就立即终止线程
while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
// 执行任务
try {
log.info("正在执行...,{}", task);
task.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
task = null;
}
}
// 退出集合,说明这个线程已经执行完所有的方法了,就要移除掉
synchronized (workers) {
log.info("worker被移除,{}", this);
workers.remove(this);
}
}
}
}
@Slf4j
class BlockingQueue<T> {
public BlockingQueue(int queueCapacity) {
this.capacity = queueCapacity;
}
// 1.定义阻塞队列
private Deque<T> queue = new ArrayDeque<>();
// 2.因为是高并发,所以要有锁
private ReentrantLock lock = new ReentrantLock();
// 3.定义一个容量的大小
private int capacity;
// 4.定义一个队列满的时候的状态量,表示,不能向队列中放元素了
private Condition fullWaitState = lock.newCondition();
// 5.定义一个队列空的状态量,表示,线程不能再消费了
private Condition emptyWaitState = lock.newCondition();
// 带超时时间的获取
public T poll(long timeout, TimeUnit unit) {
// 加锁取
lock.lock();
// 将时间转化成为统一的时间
long nanos = unit.toNanos(timeout);
try {
// 队列不能是空
while (queue.isEmpty()) {
// 阻塞
try {
if (nanos <= 0) {
return null;
}
// 返回值是剩下的等待时间
nanos = emptyWaitState.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不空,就能拿元素
T t = queue.removeFirst();
// 有位置了,可以往里放元素了
fullWaitState.signal();
return t;
} finally {
lock.unlock();
}
}
// 从队列中取出的方法,阻塞获取
public T take() {
// 加锁取
lock.lock();
try {
// 队列不能是空
while (queue.isEmpty()) {
// 阻塞
try {
emptyWaitState.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不空,就能拿元素
T t = queue.removeFirst();
// 有位置了,可以往里放元素了
fullWaitState.signal();
return t;
} finally {
lock.unlock();
}
}
// 相对列中放入元素的方法
public void put(T element){
lock.lock();
try {
// 是不是满了
while (queue.size() == capacity) {
// 满了,阻塞
try {
fullWaitState.await();
log.info("正在等待添加到任务队列...,{}", element);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不满了,就能放进去了
queue.addLast(element);
log.info("放入队列,任务:{}", element);
// 如果现在又因为队列是空的而阻塞的线程,就唤醒
emptyWaitState.signal();
} finally {
lock.unlock();
}
}
// 带超时时间阻塞添加
public boolean poll(T element, long timeout, TimeUnit unit) {
lock.lock();
long nacos = unit.toNanos(timeout);
try {
// 是不是满了
while (queue.size() == capacity) {
// 满了,阻塞
try {
if (nacos <= 0) {
return false;
}
nacos = fullWaitState.awaitNanos(nacos);
log.info("正在等待添加到任务队列...,{}", element);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不满了,就能放进去了
queue.addLast(element);
log.info("放入队列,任务:{}", element);
// 如果现在又因为队列是空的而阻塞的线程,就唤醒
emptyWaitState.signal();
return true;
} finally {
lock.unlock();
}
}
// 当前队列容量
public int size() {
return capacity;
}
public void tryPut(T task, RejectPolicy<T> rejectPolicy) {
lock.lock();
try {
if (queue.size() < capacity) {
queue.addLast(task);
log.info("放入队列,任务:{}", task);
// 如果现在又因为队列是空的而阻塞的线程,就唤醒
emptyWaitState.signal();
} else {
rejectPolicy.reject(this, task);
}
} finally {
lock.unlock();
}
}
}
ThreadPoolExecutor

线程池状态
ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量

shutdown只是不再接收新的任务了,但是还是会把当前的任务以及阻塞队列中的任务执行完的
构造方法
在阿里巴巴手册中,明确了不能使用Executors这个工具类来定义线程池。所以就要必要搞清楚线程池的构造方法每个参数的含义
public ThreadPoolExecutor(int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数,核心线程数+应急线程数=最大线程数
long keepAliveTime, // 应急线程存活的时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 阻塞队列
ThreadFactory threadFactory, // 线程工厂,主要就是给线程起名字的
RejectedExecutionHandler handler) // 拒绝策略
keepAliveTime和unit是相对于应急线程说的,对于核心线程是没有存活时间这个概念的
线程池中任务执行的具体流程
-
初始时,线程池中没有任务,当一个任务进入线程池后,线程池会创建一个线程来执行任务。
-
当线程池中的线程数量到达了corePoolSize,这个时候再加入到线程池中的任务就会被放在阻塞队列中排队,直到有核心线程空闲出来。
-
如果在阻塞队列满之前都没有核心线程空闲,那么线程池就会创建maximumPoolSize - corePoolSize 的应急线程来处理新加入进来的任务
-
如果线程池中的线程已经到达了maximumPoolSize,仍然有新的任务进来的话,线程池就会执行拒绝策略。jdk默认提供了四种拒绝策略:
- AbortPolicy :让调用者抛出异常:RejectedExecutionException,也是默认的拒绝策略
- CallerRunsPolicy:让调用者自己运行任务
- DiscardPolicy:放弃本次任务
- DiscardOldestPolicy:放弃队列中最早的任务,本任务取而代之
-
当任务的高峰过去之后,也就是核心线程有空余的或者阻塞队列不满,应急线程这时候就无事可做。等到了keepAliveTime和unit约定的时间后,应急线程会释放。
根据这个构造方法,JDK Executors 类中提供了众多工厂方法来创建各种用途的线程池
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看到这种线程池的特点有:
- 没有应急线程
- 阻塞队列是无界的,可以放任意多的任务。
因为阻塞队列是无界的,如果不停的往里放任务,可能造成OOM
适用于任务量已知,相对耗时的任务
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(() -> {
log.info("1");
});
pool.execute(() -> {
log.info("2");
});
pool.execute(() -> {
log.info("3");
});
}

ThreadFactory自定义线程名称
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}

@Slf4j
public class TestThreadPoolExecutors {
public static void main(String[] args) {
// 线程工厂,就是为了给线程起好名字
ExecutorService selfNamePool = Executors.newFixedThreadPool(4, new ThreadFactory() {
private AtomicInteger t = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "myThreadPool-" + t.getAndIncrement());
}
});
selfNamePool.execute(() -> {
log.info("1");
});
selfNamePool.execute(() -> {
log.info("2");
});
selfNamePool.execute(() -> {
log.info("3");
});
}
}

newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
这种线程池的特点:
- 没有核心线程,全是应急线程。
- 应急线程的存活时间是60秒
- 应急线程可以无限创建
- 这个阻塞队列也很特殊
SynchronousQueue这个队列类型,除了第一元素能放入到队列中,其他的元素都要一拿一放,一手交钱,一手交货才行。
@Slf4j
public class TestSynchronousQueue {
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<Integer> tasks = new SynchronousQueue<>();
new Thread(() -> {
try {
log.info("putting...{}", 1);
tasks.put(1);
log.info("putted...{}", 1);
log.info("putting...{}", 2);
tasks.put(2);
log.info("putted...{}", 2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
try {
log.info("taking...{}", 1);
tasks.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t2").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
try {
log.info("taking...{}", 2);
tasks.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t3").start();
}
}
一拿一放

适合任务数比较密集,但每个任务执行时间较短的情况
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
这种线程池的特点是:
- 核心线程数等于最大线程数,也就是没有应急线程
- 也是无界阻塞队列
适用场景:希望任务可以串行执行的时候可以考虑这个线程池。
自己创建一个线程串行执行任务和newSingleThreadExecutor的区别
- 自己创建的线程如果执行过程中,有异常产生的话,就会结束执行,没有补救措施。
- newSingleThreadExecutor这种方式,如果执行过程中有异常产生了,那么线程池会再创建一个线程,进行补救
@Slf4j
public class TestNewSingleThreadExecutor {
public static void main(String[] args) {
new Thread(() -> {
log.info("1");
log.info("2");
int i = 1 / 0;
log.info("3");
}, "t1").start();
}
}

@Slf4j
public class TestNewSingleThreadExecutor {
public static void main(String[] args) {
/*new Thread(() -> {
log.info("1");
log.info("2");
int i = 1 / 0;
log.info("3");
}, "t1").start();*/
ExecutorService threadPool = Executors.newSingleThreadExecutor();
threadPool.execute(() -> {
log.info("1");
});
threadPool.execute(() -> {
log.info("2");
int i = 1 / 0;
});
threadPool.execute(() -> {
log.info("3");
});
}
}

newSingleThreadExecutor和newFixedThreadExecutor(1)的区别
还是从构造器说起:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
返回的对象是ThreadPoolExecutor类型的,但是这个类是ExecutorService的实例,也就是有一个引用指向了ThreadPoolExecutor这个类。
正常情况下,执行调用ExecutorService中定义的方法:

但是如果向下转型,就是ThreadPoolExecutor类型的,那能执行的方法就多了。其中有一个叫设置线程数量的方法,也就是后面线程数量是能变的。并不是1个不变的。

newSingleThreadExecutor的构造器
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
返回的是FinalizableDelegatedExecutorService类型
static class FinalizableDelegatedExecutorService
extends DelegatedExecutorService {
FinalizableDelegatedExecutorService(ExecutorService executor) {
super(executor);
}
protected void finalize() {
super.shutdown();
}
}
看这个类DelegatedExecutorService,没有提供很丰富的方法
static class DelegatedExecutorService extends AbstractExecutorService {
private final ExecutorService e;
DelegatedExecutorService(ExecutorService executor) { e = executor; }
public void execute(Runnable command) { e.execute(command); }
public void shutdown() { e.shutdown(); }
public List<Runnable> shutdownNow() { return e.shutdownNow(); }
public boolean isShutdown() { return e.isShutdown(); }
public boolean isTerminated() { return e.isTerminated(); }
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
return e.awaitTermination(timeout, unit);
}
public Future<?> submit(Runnable task) {
return e.submit(task);
}
public <T> Future<T> submit(Callable<T> task) {
return e.submit(task);
}
public <T> Future<T> submit(Runnable task, T result) {
return e.submit(task, result);
}
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
return e.invokeAll(tasks);
}
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException {
return e.invokeAll(tasks, timeout, unit);
}
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
return e.invokeAny(tasks);
}
public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return e.invokeAny(tasks, timeout, unit);
}
}
所以
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改 。
FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因 此不能调用
ThreadPoolExecutor 中特有的方法 Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
提交任务

以下方法不一一测试了
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
测试部分
public class TestSubmit {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(1);
// 输出1
threadPool.submit(() -> {
System.out.println(1);
});
// 输出ok
Future<String> result = threadPool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "ok";
}
});
String s = result.get();
// 输出执行结束
Future<String> result1 = threadPool.submit(() -> {
}, "执行结束");
System.out.println(result1.get());
}
}
关闭线程池

shutdown
线程池状态变为shutdown
不会接收新的任务
但已经提交过的任务会执行完
不会阻塞调用线程的执行
public class TestShutdown {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(() -> {
System.out.println(1);
});
executorService.execute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(2);
});
executorService.execute(() -> {
System.out.println(3);
});
System.out.println("shutdown");
executorService.shutdown();
executorService.execute(() -> {
System.out.println(4);
});
}
}
结果,2输出出来了,但是4没有输出出来

shutdownNow
线程池状态变为 STOP
不会接收新任务
会将队列中的任务返回
并用 interrupt 的方式中断正在执行的任务
public class TestShutdown {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(() -> {
System.out.println(1);
});
executorService.execute(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(2);
});
executorService.execute(() -> {
System.out.println(3);
});
System.out.println("shutdown");
List<Runnable> runnables = executorService.shutdownNow();
System.out.println("=============");
runnables.forEach(Runnable::run);
}
}
测试结果,打断了

判断线程池中的线程是否全部都结束了,可以用如下方法
boolean isTerminated();
在调用shutdown()方法之后,线程池中的线程是有可能继续执行任务的,但是主线程是不会等着线程池中的线程执行完之后再结束的。如果主线程想在线程池中的线程全部结束之后做点事情的话。可以调用下边的方法。
// 时间表示等待的时间,其实可以用Callable接口来获得Future类型,然后调用get()方法,也能阻塞主线程。
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
模式之工作线程(Work Thread)
定义
让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务。也可以将其归类为分工模式,它的典型实现 就是线程池,也体现了经典设计模式中的享元模式(复用)。
例如,海底捞的服务员(线程),轮流处理每位客人的点餐(任务),如果为每位客人都配一名专属的服务员,那 么成本就太高了(对比另一种多线程设计模式:Thread-Per-Message)
注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率
例如,如果一个餐馆的工人既要招呼客人(任务类型A),又要到后厨做菜(任务类型B)显然效率不咋地,分成 服务员(线程池A)与厨师(线程池B)更为合理,当然你能想到更细致的分工.
饥饿
固定大小线程池会有饥饿现象
- 两个工人是同一个线程池中的两个线程
- 他们要做的事情是:为客人点餐和到后厨做菜,这是两个阶段的工作
- 客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待
- 后厨做菜:没啥说的,做就是了
- 比如工人A 处理了点餐任务,接下来它要等着 工人B 把菜做好,然后上菜,他俩也配合的蛮好
- 但现在同时来了两个客人,这个时候工人A 和工人B 都去处理点餐了,这时没人做饭了,饥饿
正常情况下,两个线程,分别左一个人的点菜,做菜
@Slf4j
public class TestHungry {
// 菜品
public static final List<String> FOOD_MENU = Arrays.asList("西红柿炒鸡蛋", "肥牛汤锅", "黄焖鸡米饭", "冒菜");
public static final Random RANDOM = new Random();
public static void main(String[] args) {
// 创建线程,两个
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(() -> {
log.info("点菜...");
Future<String> result = pool.submit(() -> {
log.info("做菜");
return FOOD_MENU.get(RANDOM.nextInt(FOOD_MENU.size()));
});
try {
log.info("{}做好了", result.get());
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
结果

但是现在来两个人呢
@Slf4j
public class TestHungry {
// 菜品
public static final List<String> FOOD_MENU = Arrays.asList("西红柿炒鸡蛋", "肥牛汤锅", "黄焖鸡米饭", "冒菜");
public static final Random RANDOM = new Random();
public static void main(String[] args) {
// 创建线程,两个
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(() -> {
log.info("点菜...");
Future<String> result = pool.submit(() -> {
log.info("做菜");
return FOOD_MENU.get(RANDOM.nextInt(FOOD_MENU.size()));
});
try {
log.info("{}做好了", result.get());
} catch (Exception e) {
e.printStackTrace();
}
});
pool.execute(() -> {
log.info("点菜...");
Future<String> result = pool.submit(() -> {
log.info("做菜");
return FOOD_MENU.get(RANDOM.nextInt(FOOD_MENU.size()));
});
try {
log.info("{}做好了", result.get());
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
结果,两个线程都在点菜,没人做菜了,就产生了饥饿问题。

解决办法,可以多几个线程,多一个就行修改代码ExecutorService pool = Executors.newFixedThreadPool(3);

但是这种扩展性不强,不推荐。
应该使用不同的任务类型使用不容的线程池这种方式
@Slf4j
public class TestHungry {
// 菜品
public static final List<String> FOOD_MENU = Arrays.asList("西红柿炒鸡蛋", "肥牛汤锅", "黄焖鸡米饭", "冒菜");
public static final Random RANDOM = new Random();
public static void main(String[] args) {
// 创建线程,两个
ExecutorService waiterPool = Executors.newFixedThreadPool(1);
ExecutorService cookPool = Executors.newFixedThreadPool(1);
waiterPool.execute(() -> {
log.info("点菜...");
Future<String> result = cookPool.submit(() -> {
log.info("做菜");
return FOOD_MENU.get(RANDOM.nextInt(FOOD_MENU.size()));
});
try {
log.info("{}做好了", result.get());
} catch (Exception e) {
e.printStackTrace();
}
});
waiterPool.execute(() -> {
log.info("点菜...");
Future<String> result = cookPool.submit(() -> {
log.info("做菜");
return FOOD_MENU.get(RANDOM.nextInt(FOOD_MENU.size()));
});
try {
log.info("{}做好了", result.get());
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
结果

创建多少线程池合适
- 过小会导致程序不能充分地利用系统资源、容易导致饥饿
- 过大会导致更多的线程上下文切换,占用更多内存
CPU密集型运算
通常采用 cpu 核数 + 1 能够实现最优的 CPU 利用率,+1 是保证当线程由于页缺失故障(操作系统)或其它原因 导致暂停时,额外的这个线程就能顶上去,保证 CPU 时钟周期不被浪费
I/O密集型运算
CPU 不总是处于繁忙状态,例如,当你执行业务计算时,这时候会使用 CPU 资源,但当你执行 I/O 操作时、远程 RPC 调用时,包括进行数据库操作时,这时候 CPU 就闲下来了,你可以利用多线程提高它的利用率。
经验公式如下:
线程数 = 核数 * 期望 CPU 利用率 * 总时间(CPU计算时间+等待时间) / CPU 计算时间
例如 4 核 CPU 计算时间是 50% ,其它等待时间是 50%,期望 cpu 被 100% 利用,套用公式
4 * 100% * 100% / 50% = 8
任务调度线程池(newScheduledThreadPool)
让线程池执行任务延迟执行
可以使用 java.util.Timer 来实现定时功能
Timer
优点是简单易用
缺点是所有的任务都是串行执行,效率低。如果执行期间产生异常,主线程会终止。后续的任务就无法处理。
串行执行
@Slf4j
public class TestTimer {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask timerTask1 = new TimerTask() {
@Override
public void run() {
log.info("task1...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
TimerTask timerTask2 = new TimerTask() {
@Override
public void run() {
log.info("task2...");
}
};
log.info("starting...");
timer.schedule(timerTask1, 1000);
timer.schedule(timerTask2, 1000);
}
}
如果不是串行执行的话,那么task2...的输出时间应该比starting...晚一秒
实际上晚了3秒,说明是等task1完成之后才执行的task2

异常
在task1中手动加一个异常1/0
@Slf4j
public class TestTimer {
public static void main(String[] args) {
Timer timer = new Timer();
TimerTask timerTask1 = new TimerTask() {
@Override
public void run() {
log.info("task1...");
int i = 1 / 0;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
TimerTask timerTask2 = new TimerTask() {
@Override
public void run() {
log.info("task2...");
}
};
log.info("starting...");
timer.schedule(timerTask1, 1000);
timer.schedule(timerTask2, 1000);
}
}
报错了,且任务2没有完成

使用newScheduledThreadPool改写
不是串行
@Slf4j
public class TestNewScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
log.info("starting...");
pool.schedule(() -> {
log.info("task1...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1, TimeUnit.SECONDS);
pool.schedule(() -> {
log.info("task2...");
}, 1, TimeUnit.SECONDS);
}
}
结果,看到时间显示,task1的睡眠2秒并没有影响task2

异常
手动添加异常
@Slf4j
public class TestNewScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
log.info("starting...");
pool.schedule(() -> {
log.info("task1...");
int i = 1 / 0;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1, TimeUnit.SECONDS);
pool.schedule(() -> {
log.info("task2...");
}, 1, TimeUnit.SECONDS);
}
}
结果,正常执行,且没有报错。

但是在程序中,异常是一定要处理的
处理异常的方式
- 自己捕获
@Slf4j
public class TestNewScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
log.info("starting...");
pool.schedule(() -> {
log.info("task1...");
try {
int i = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 1, TimeUnit.SECONDS);
pool.schedule(() -> {
log.info("task2...");
}, 1, TimeUnit.SECONDS);
}
}
成功捕获异常

- 利用Future.get()将异常输出
@Slf4j
public class TestNewScheduledThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
log.info("starting...");
ScheduledFuture<Boolean> result = pool.schedule(() -> {
log.info("task1...");
int i = 1 / 0;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}, 1, TimeUnit.SECONDS);
log.info(String.valueOf(result.get()));
}
}
可以看到,将异常输出出来了。

使用newScheduledThreadPool来创建定时任务
每隔一秒钟执行一次任务
log.info("starting...");
pool.scheduleAtFixedRate(() -> {
log.info("task1...");
}, 1, 1, TimeUnit.SECONDS);
结果

这个方法虽然是延迟一秒执行,但是还是要看方法的具体执行时间的,如果执行时间大于了延迟的时间,那么方法执行完就会立即执行,如果小于延迟时间,是会等到设定的延迟时间的。
log.info("starting...");
pool.scheduleAtFixedRate(() -> {
log.info("task1...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, 1, 1, TimeUnit.SECONDS);
第一次执行,18-17=1延迟了一秒,但是这个时候再要执行的时候,发现,前一个还没执行完,就会等执行完,然后立刻执行。

但是这个方法
pool.scheduleWithFixedDelay(() -> {
log.info("task1...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, 1, 1, TimeUnit.SECONDS);
会在前一个方法执行完之后再延迟的,所以就是隔三秒执行一次

scheduleAtFixedRate在前一个周期开始执行的时候就已经开始延迟了,但是延迟到了1秒之后,发现前一个还在执行。就等执行完
scheduleWithFixedDelay在前一个周期执行结束之后才开延迟,所以会延迟3秒
定时任务
public class FixTimeTask {
// 每周四晚上六点执行任务
// 分析现在到周四要延迟多少
// 然后间隔多少
// 全部以毫秒为单位
public static void main(String[] args) {
// 间隔时间,一周的时间
long intervalTime = 1000 * 60 * 60 * 24 * 7;
// 现在的时间
LocalDateTime now = LocalDateTime.now();
// 到周四的时间
LocalDateTime time = now.withNano(0).withSecond(0).withMinute(0).withHour(18).with(DayOfWeek.THURSDAY);
// 但是有个问题,如果今天已经是周五了,那么time显示的还是这周的周四,很明显不对,应该是下周,所以要判断下
if (now.isAfter(time)) {
time = time.plusWeeks(1);
}
long delayTime = Duration.between(now, time).toMillis();
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
pool.scheduleAtFixedRate(() -> {}, delayTime, intervalTime, TimeUnit.MILLISECONDS);
}
}
最后
根据黑马满老师讲的并发编程整理,满老师讲非常的nice
https://www.bilibili.com/video/BV16J411h7Rd/?spm_id_from=333.337.search-card.all.click

浙公网安备 33010602011771号