线程池
01 / ThreadPoolExecutor
1.1实现机制
线程每次new都要和操作系统申请资源,且用完后就烧毁了,没有复用性
处理任务四大步:
1、当前已有的线程数有没有达到核心的线程数
2、已经达到了核心线程数,将任务放到队列里面去
3、当前并发太大,队列满了,再提交任务,判断最大线程数有没有达到,没有就创建核心线程之外的线程
4、达到最大线程数后,满负荷时,拒绝任务
拒绝有四个策略:
1、谁提交的任务,谁自己去执行(主线程)
2、直接抛异常
3、放弃任务,不管了
4、还要执行新执行的任务,放弃队头的任务(放入队列时间最长的任务)
由核心线程–>队列—>最大线程的扩展顺序
1.2核心参数
1.3生命周期
1.线程池的五种状态,只能由小到大迁移,即-1>0>1>2>3。
2.shutdown(不清空任务队列、 会等它们完成,shutdownNow)会清 空任务队列、不等它们完成。shutdown()只中断空闲的线程,shutdownNow()会 中断所有的线程,不管你执行没执行完。
3.TIDYING 和TREMINATED二者之间执行了一个钩子函数terminated(,目前这是-个空的实现。
1.4阅读源码
实现机制: execute()
核心参数:实例变量
生命周期:静态常量
拒绝策略: CallerRunsPolicy, AbortPolicy, DiscardPolicy, DiscardOldestPolicy
提交方法: execute(), submit(), newTaskFor(), FutureTask
execute:
//没达到核心线程数,就创建线程,达到了就加入到队列里,加入队列失败,再次创建线程(非核心线程),如果还是失败,就拒绝
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();//获取线程状态
if (workerCountOf(c) < corePoolSize) {//线程数量是否小于核心线程数
if (addWorker(command, true))//加到线程里,核心线程的身份
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {//已到核心线程数,线程池还在运行,将任务加到队列里
int recheck = ctl.get();//检验
if (! isRunning(recheck) && remove(command))//如果不是运行状态就一处
reject(command);//拒绝
else if (workerCountOf(recheck) == 0)//核心线程数等于0
addWorker(null, false);//创建一个线程
}
else if (!addWorker(command, false))
reject(command);
}
addWorker:添加一个线程
添加一个任务,看是不是核心任务
retry:外层循环的名字
break retry:跳出循环
//创建一个线程,将其加入线程池,加入成功就运行
for (;;) {
int wc = workerCountOf(c);
//获取工作线程的数量,超过(核心或者普通线程数)限制,返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
//workers是一个容器就是一个线程池的体现,线程加入成功就运行
Worker:封装线程和任务
线程池的内部类继承AQS实现了Runnable接口
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
线程池里面的线程复用
不管是核心线程还是非核心线程,空闲下来后都会去队列里面拿任务执行。没有任务就还是空闲,超过一定时间就会被释放。
线程池里面一定要有一定数量的线程,保证任务到来时,能够处理
execute是没有返回值的,调用的任务也是要没有返回值的。那怎么线程池调用一个有返回值的任务呢?
submit
适配器模式:把A接口变成B接口,为什么要把A接口变成B接口?
例如:我有一个狗和人的接口,程序中想调用人的玩方法,但是只能传入狗。这个时候就需要将狗是配成人。继承狗的实现类实现人接口的方法。(继承原实现目标)
//将Callable适配成Runnable
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);//将task转为Runnable
execute(ftask);//利用execute执行
return ftask;
}
submit传进了Callable,将Callable转为Runnable再调用 execute
class FutureTask<V> implements RunnableFuture<V> //继承Runnable接口的子接口,需要重写run方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
02 / ScheduledThreadPoolExecutor
2.1核心功能
2.2阅读源码
1.延迟执行任务:
DelayedWorkQueue, ScheduledThreadPoolExecutor(). schedule()
2.周期执行任务:
scheduleAtFixedRate(), scheduleWithFixedDelay(). ScheduledFutureTask
怎么实现的延迟?
在出队的时候加以判断,如果时间到了就正常出队,要是时间没到,就阻塞
static class DelayedWorkQueue extends AbstractQueue<Runnable>
implements BlockingQueue<Runnable> {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
return finishPoll(first);
first = null; // don't retain ref while waiting
if (leader != null)
available.await();
构造器:
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
核心线程数,,,,队列,对于这个线程池队列必须是延迟队列,利用延迟队列来实现延迟周期性的动作
延迟执行逻辑:
将延迟和任务封装打包,将打包的数据加入队列,队列是延迟的,一定是到时 间了才执行
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
//将任务和延迟时间封装成对象,将封装之后的任务,将其加入队列
周期执行逻辑:
将任务和时间封装之后的任务加入延迟队列
scheduleAtFixedRate:
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
scheduleWithFixedDelay:
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(-delay));
两者唯一的区别就是:对周期的处理不一样,一正一负
先去检查状态再去检查类型,再决定使用什么样的run方法(不是周期性的,那就调父类的run方法,不加周期的处理)
03 / Executors
Executors采用无界队列,容易造成OOM,不建议使用。
并发工具
01 / Semaphore
Semaphore就是信号量,提供了资源数量的并发访问控制。假设初始化Semaphore时指定的资源数量是10,那么若有N个线程来获取Semaphore里面的资源,N个线程中只有10个线程能获取到,其他线程都会阻塞。直到有线程释放了资源,其他线程才能获取到。
当初始资源的个数为1的时候,Semaphore退化为 排他锁。Semaphone的实现原 理和锁十分类似,也是基于AQS,也有公平和非公平之分。
Sync是AQS的子类继承AQS,它就是一个同步器
02 / CountDownLatch
主线程要等待N个Worker线程工作完毕才退出,就可以使用CountDownLatch来实现。
CountDownL atch的原理和Semaphore原理类似,同样是基于AQS,不过没有公平和非公平之分。
03 / CyclicBarrier
CountDownLatch的计数器是一次性的, 也就是等到计数器值变为0后,再调用CountDownLatch的await()和countDown()方法都会立刻返回,此时的CountDownLatch就起不到线程同步 的效果了。
为了满足计数器可以重置的需要,JDK开 发组提供了CyclicBarrier类,并 且CyclicBarrier类的功能并不限于CountDownL atch的功能。从字面意思理解,CyclicBarrier是回环屏障的意思, 它可以让一组线程全部达到一个状态后再全部同时执行。
之所以叫作回环是因为当所有等待线程执行完毕,并重置CyclicBarrier的状态后它可以被重用。
变为0后,再调用CountDownLatch的await()和countDown()方法都会立刻返回,此时的CountDownLatch就起不到线程同步 的效果了。
为了满足计数器可以重置的需要,JDK开 发组提供了CyclicBarrier类,并 且CyclicBarrier类的功能并不限于CountDownL atch的功能。从字面意思理解,CyclicBarrier是回环屏障的意思, 它可以让一组线程全部达到一个状态后再全部同时执行。
之所以叫作回环是因为当所有等待线程执行完毕,并重置CyclicBarrier的状态后它可以被重用。
之所以叫作屏障是因为线程调用await()方法后就会被阻塞,这个阻塞点就称为屏障点,等所有线程都调用了await()方法后,线程就会突破屏障,继续执行。