基本认识
jdk中提供了Executors框架获取几类线程池,经典的四类single(单例),fixed(固定),cache(缓存),Schedul(定时)。工作方式(如果不满足就流向下一步),创建核心线程---->队列存储--->创建救急线程--->拒绝策越。
其中单例和固定线程池队列采用LinkBlockQueue无界队列当池中没有可用的线程时,优先放入队列中,cache使用同步有界队列,该队列特点是无容量,即有任务就创建线程不会往队列放,Schedul采用延迟队列,该队列会对存放的任务加上执行优先级属性。
//ExecutorService executor = Executors.newFixedThreadPool(2); //2个线程 无界队列
//ExecutorService executor = Executors.newSingleThreadExecutor(); //1个线程 无界队列
//ExecutorService executor = Executors.newCachedThreadPool(); //5个线程 有界同步队列
//ExecutorService executor = Executors.newScheduledThreadPool(3);
常用方法
submit(Callable task)----->执行Callable任务,返回Future对象。
execute (Runnable tasj)------>执行Runbale任务,无返回值。
invokeAll(Collection<Callable> tasks)---->执行批量Callable任务,返回List<Future>对象,代表每个任务的执行结果。
invokeAny(Collection<Callable> tasks) ---->执行批量Callable任务中的一个,返回object对象,代表执行任务的执行结果。
shutdown()--->停止线程池,如果任务已经在池中则会执行完才停止。
shutdownNow()--->停止线程池,如果任务已经在池中则会强制停止。
底层原理及自定义线程池实现
线程池组成部分,核心线程大小,最大线程大小,救急线程过期时间,救急线程时间单位,阻塞队列,拒绝策越,线程工厂名。根据这些其实本地可以实现一个简单线程池,主要是针对阻塞队列的设计,线程类的设计。
阻塞队列:
设计思路:成员变量有队列大小,基本队列,当队列满时,外部任务应等待队列中的任务取出执行完才放入队列,否则阻塞。同理队列任务为空时,超过一定时间,线程池就关闭。执行任务的过程中需要加锁放置多个线程执行同一任务,还需要两个waitSet放阻塞的任务。所以还需要一个锁,两个WaitSet。方法:入队,出队,获取队列长度。代码如下
/**
* 阻塞队列
* 设计 有界,有序,push及pull应保证线程安全
*/
@Slf4j
class MyBolckQueue<T> {
private Deque<T> deque = new ArrayDeque<>();
private ReentrantLock lock = new ReentrantLock();
private Condition proSet = lock.newCondition();
private Condition consumeSet = lock.newCondition();
private int capCity;
public MyBolckQueue(int capCity) {
this.capCity = capCity;
}
/**
* 拉取消息,如果消息为空,则消费者进入等待,如果消费了消息,则唤醒生产者队列可继续放任务
*
* @param
*/
public T poll(long timeout, TimeUnit unit) {
lock.lock();
try {
// 将 timeout 统一转换为 纳秒
long nanos = unit.toNanos(timeout);
while (deque.isEmpty()) {
try {
// 返回值是剩余时间
if (nanos <= 0) {
return null;
}
nanos = consumeSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = deque.removeFirst();
proSet.signal();
return t;
} finally {
lock.unlock();
}
}
/**
* 存放任务,如果队列满了,则生产者等待。存入任务后需通知消费者消息
*
* @return
*/
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
// 判断队列是否满
if (deque.size() == capCity) {
rejectPolicy.reject(this, task);
} else { // 有空闲
log.debug("加入任务队列 {}", task);
deque.addLast(task);
consumeSet.signal();
}
log.info("此时队列大小{}", deque.size());
} finally {
lock.unlock();
}
}
线程类
设计思路:成员变量有Runable任务,方法有run方法重写即可。构造方法接收任务对象。
class Worker extends Thread {
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
//此处的run需要不断去执行新任务或者从队列中取任务
while (task != null || (task = taskQueue.poll(1, timeUnit)) != null) {
try {
log.debug("正在执行...{}", task);
task.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
task = null;
}
}
synchronized (workers) {
log.debug("worker 被移除{}", this);
workers.remove(this);
}
}
}
线程池类
设计思路:包含线程集合,核心线程大小,超时时间,时间单位,拒绝策越,阻塞队列。其中线程基本对象,阻塞队列已经设计好,拒绝策越是函数式接口,其它都是基本属性。方法有execute()方法执行任务,判断逻辑跟JDK线程池逻辑一致。代码如下
@Slf4j
class ThreadPool {
private MyBolckQueue<Runnable> taskQueue;
private HashSet<Worker> workers = new HashSet<>();
private int coreSize;
private TimeUnit timeUnit;
private long expireTime;
private RejectPolicy rejectPolicy;
public ThreadPool(MyBolckQueue<Runnable> taskQueue, int coreSize, TimeUnit timeUnit, long expireTime, RejectPolicy rejectPolicy) {
this.taskQueue = taskQueue;
this.coreSize = coreSize;
this.timeUnit = timeUnit;
this.expireTime = expireTime;
this.rejectPolicy = rejectPolicy;
}
/**
* execute提交一个Runable任务
*
* @param task
*/
public void excute(Runnable task) {
//如果工作线程小于核心线程数,则可以创建一个线程对象,否则进入队列等待
synchronized (workers) {
//此处加锁,避免执行同一个任务
if (workers.size() < coreSize) {
Worker worker = new Worker(task);
log.info("创建核心线程{}运行任务{}", worker, task);
workers.add(worker);
worker.start();
} else {
//尝试加入阻塞队列,如果不成功执行拒绝策越
taskQueue.tryPut(rejectPolicy, task);
}
}
}
}
拒绝策越及测试方法
@Slf4j
public class ExcutorPools {
public static void main(String[] args) {
MyBolckQueue objectMyBolckQueue = new MyBolckQueue<>(1);
ThreadPool pool = new ThreadPool(
objectMyBolckQueue,
2,
TimeUnit.SECONDS,
1,
(objectMyBolckQueue1, runnable1) -> {
log.info("结束等待{}", runnable1);
}
);
for (int i = 0; i < 4; i++) {
int j = i;
pool.excute(() -> {
log.debug("{}", j);
});
}
}
}
@FunctionalInterface // 拒绝策略
interface RejectPolicy<T> {
void reject(MyBolckQueue<T> queue, T task);
}
测试结果:
任务0,1创建核心线程来执行。任务2发现核心线程已有2个,进入阻塞队列。任务3发现队列也满,执行拒绝策越。任务0,1执行完成后,取队列任务执行。最终一段时间没任务,线程销毁。