一、自定义线程池
任务队列
class BlockingQueue<T> {
// 任务队列
private Deque<T> queue = new ArrayDeque<>();
// 锁
private ReentrantLock lock = new ReentrantLock();
// 生产者条件变量
private Condition fullWaitSet = lock.newCondition();
// 消费者条件变量
private Condition emptyWaitSet = lock.newCondition();
// 容量
private int capcity;
// 阻塞获取
public void take(T element) {
lock.lock();
try {
while(queue.isEmpty()) {
try {
emptyWaitSet .await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
return t;
} finally {
lock.unlock();
}
}
// 带超时的阻塞获取
public T poll(Long timeout, TimeUnit unit) {
lock.lock();
try {
// 将timeout转化为纳秒
long nanos = unit.toNanos(timeout);
while(queue.isEmpty()) {
try {
if(nanos <= 0) {
return null;
}
// 返回剩余时间
nanos = emptyWaitSet.await(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
return t;
} finally {
lock.unlock();
}
}
// 阻塞添加
public void put(T element) {
lock.lock();
try {
while(queue.size() == capcity) {
try {
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(element);
emptyWaitSet.signal();
} finally {
lock.unlock();
}
}
// 带超时时间阻塞添加
public boolean offer(T task, long timeout, TimeUnit timeUnit) {
lock.lock();
try {
long nanos = timeUnit.toNanos(timeout);
while(queue.size() == capcity) {
try {
if(nanos <= 0) {
return false;
}
nanos = fullWaitSet.awaitNaos(nanos );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(element);
emptyWaitSet.signal();
} finally {
lock.unlock();
}
}
// 获取大小
public int size() {
lock.lock();
try {
return queue.size();
} finally {
lock.unlock();
}
}
// 带拒绝策略的put
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
if(queue.size() == capcity) {
rejectPolicy.reject(this,task);
} else {
queue.addLast(task);
emptyWaitSet.signal();
}
} finally {
lock.unlock();
}
}
}
线程池
class ThreadPool {
// 任务队列,需要执行的任务队列
private BlockingQueue<Runnable> taskQueue;
// 线程集合
private HashSet<Worker> workers = new HashSet<>();
// 核心线程数
private int coreSize;
// 超时时间
private long timeout;
// 单位
private TimeUnit timeUnit;
private RejectPolicy<Runnable> rejectPolicy;
public ThreadPool(int coreSize,long timeout,TimeUnit timeUnit,int queueCapcity, RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.taskQueue = new BlockingQueue<>(queueCapcity);
this.rejectPolicy = rejectPolicy;
}
class Worker extends Thread{
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
while(task != null || (task = taskQueue.take()) != null){
try {
task.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
task = null;
}
}
synchronized(workers){
workers.remove(this);
}
}
}
public void execute(Runnable task) {
synchronized(workers){
if (workers.size() < coreSize){
Worker worker = new Worker(task);
workers.add(worker)
worker.start();
} else {
// 没使用拒绝策略的put,就是一直死等
// taskQueue.put(task);
// 带有自定义拒绝策略的put,队列满时根据拒绝策略进行处理
taskQueue.tryPut(rejectPolicy,task);
}
}
}
}
拒绝策略
@FunctionalInterface // 拒绝策略
interface RejectPolicy<T> {
void reject(BlockingQueue<T> queue,T task);
}
二、JDK线程池
1. 线程池状态
ThreadPoolExecutor使用int的高3位来表示线程池状态,低29位表示线程数量。
状态名 | 高3位置 | 接收新任务 | 处理阻塞队列任务 | 说明 |
---|---|---|---|---|
RUNNING | 111 | Y | Y | |
SHUTDOWN | 000 | N | Y | 不会接收新任务,但是会处理阻塞队列剩余任务 |
STOP | 111 | N | N | 会中断正在执行的任务,并抛弃阻塞队列任务 |
TIDYING | 111 | 任务全执行完毕,活动线程为0即将进入终结 | ||
TERMINATED | 111 | 终结状态 |
// 将线程池状态和线程个数合二为一,就可以通过一次cas原子操作来辅助
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
2. 构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
① corePoolSize 核心线程数(最多保留的线程数)
② maximumPoolSize 最大线程数
③ keepAliveTime 生存时间
④ unit 时间单位
⑤ workQueue 阻塞队列
⑥ threadFactory 线程工程-可以为线程创建时起名
⑦ handler 拒绝策略
3. 工作原理
① 线程池中的线程都是懒加载,也就是刚开始线程池中并没有工作线程。
② 当有一个任务提交时,首先判断工作线程数是否小于核心线程数,如果少于则创造新的工作线程去处理任务。
③ 当工作线程数超过核心线程数时,就会加入到阻塞队列中。
④ 当阻塞队列满时,添加到阻塞队列失败,当工作线程少于最大线程数时就会创造救急线程处理任务。
⑤ 当工作线程总数等于最大线程数,阻塞队列也满时,则会进行拒绝策略。
⑥ 当线程空闲时间大于生存时间,救急线程会关闭,核心线程不会关闭。
4. 拒绝策略
如果线程到达最大线程数仍然有新任务,此时就会执行拒绝策略,jdk提供了4种拒绝策略的实现。
① AbortPolicy抛出RejectedExecutionException异常,这是默认策略
② CallerRunsPolicy 让调用者执行任务
③ DiscardPolicy 放弃本次任务
④ DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
5. Executors类
该类提供了很多工厂方法来创建各种用途的线程池
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads,
nThreads,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
固定大小的线程池,核心线程数等于最大线程数,阻塞队列是无界的,适用于任务量已知场景。
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
核心线程数时0,最大线程数是最大整数。救急线程空闲时间为60s。队列采用了SynchronousQueue,它没有容量,放置认为的线程会被阻塞住直到工作线程取到。
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
线程数固定大小为1,单线程执行。如果自己创建一个单线程执行任务,如果任务失败会导致这个线程终止,而该线程池会创建一个新的线程,保证后续正常工作。
6. 线程池相关方法
① void execute(Runnable command); 执行任务。
② <T> Future submit(Callable<T> task);
提交任务task,用返回值Future获得任务执行结果。
③ public List<Future> invokeAll(Collection<? extends Callable> tasks) ;
提交tasks中所有任务
④ public T invokeAny(Collection<? extends Callable> tasks) ;
提交tasks中所有任务,那个任务先成功执行完成,返回此任务执行结果,其他任务取消。
⑤ void shutdown();
不会接收新任务,但是已提交的任务会执行完。不会阻塞调用线程的执行
7. Timer类
在任务调度线程池功能加入之前,可以使用java.util.Timer来实现定时功能。Timer的优点在于简单易用,但由于所有任务都是由同一线程来调度,所以任务都是串行执行。
例如下图中任务1和任务2都是延迟一秒执行,但是如果任务一耗时较长,也会导致任务二必须等待任务一结束才能执行。
7. Executors的任务调度线程池
当任务数少于线程数时,是并行执行,互不干扰。如果任务数大于线程数,还是会产生Timer类的问题。
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
// 延迟执行
pool.schedule(() -> {
System.out.println("task1");
sleep(2);
},1,TimeUnit.SECONDS);
pool.schedule(() -> {
System.out.println("task2");
sleep(2);
},1,TimeUnit.SECONDS);
// 定时执行,延迟2s后执行,之后每1s执行一次。但是如果当执行任务时间大于定时时间,则按照任务执行完时间定时执行。
pool.scheduleAtFixedRate(() -> {
System.out.println("task3");
sleep(2);
},2,1,TimeUnit.SECONDS);
pool.scheduleAtFixedRate(() -> {
System.out.println("task4");
sleep(2);
},2,1,TimeUnit.SECONDS);
// 延迟2s后定时执行任务,定时时间从上一个任务结束时开始计时。例如上一个任务要3s执行完,则4s后才会执行该任务。
pool.scheduleWithFixedDelay(() -> {
System.out.println("task4");
sleep(2);
},2,1,TimeUnit.SECONDS);
例子:每周四 18:00:00定时执行任务
8.线程池处理异常
线程池中的任务如果执行过程中产生异常,也不会抛出异常,所以处理方法如下:
① 自己主动try catch
② 使用带返回值的执行方法