一、理解BlockedQueue
1. 基础方法
BlockingQueue 继承 Queue,提供了堵塞队列的相关方法。 具体用法参见具体的队列。
获得元素 | |
---|---|
take | 可中断,堵塞方法 |
poll | 可中断,提供堵塞时长,堵塞方法 |
插入元素 | |
---|---|
add | 如果队列满了,则抛出异常 |
offer | 如果队列满了,则抛出false |
put | 可中断,堵塞方法 |
2. 实现子类
ArrayBlockingQueue 由数组支持的有界队列
LinkedBlockingQueue 由链接节点支持的可选有界队列
PriorityBlockingQueue 由优先级堆支持的无界优先级队列
DelayQueue 由优先级堆支持的、基于时间的调度队列
ArrayBlockingQueue 和 LinkedBlockingQueue 都是使用ReentrantLock来加锁实现,如put方法如下所示:
public void put(E e) throws InterruptedException {
checkNotNull(e);
// 加锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try{
// 队列满了的话,则等待,这里使用while避免唤醒的时候,数据又被修改了,唤醒是指的是可以有拥有锁的资格等待操作系统被调度,并不算拥有了锁。
while(count == items.length){
notFull.await();
}
enqueue(e);
} finally{
lock.unlock();
}
}
2.1 DelayQueue 优先级堆支持的、基于时间的调度队列
基于数组最大堆、最小堆的实现方式,也就是使用上浮、下沉操作,将当前索引的值和当前索引/2的值进行比较,如果是最大堆,则如果当前索引的值大于当前索引/2的值,则交换位置,继续比较,到当前索引大于1的时候停止比较。
具体参考:堆的实现
二、理解ThreadPoolExecutor
在线程执行的时候,定义一个顶级接口,Executor ,定义接口方法execute,其中ExecutorService是定义了一个线程管理的实现方式,其中具体线程池的实现为ThreadPoolExecutor。
- 定义初始化函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
// 核心线程数
this.corePoolSize = corePoolSize;
// 最大线程数
this.maximumPoolSize = maximumPoolSize;
// 堵塞队列
this.workQueue = workQueue;
// 激活时间
this.keepAliveTime = unit.toNanos(keepAliveTime);
// 线程创建工厂类
this.threadFactory = threadFactory;
// 拒绝策略
this.handler = handler;
}
- 提交任务Runnable
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 如果小于核心线程数
if (workerCountOf(c) < corePoolSize) {
// true表示为核心线程数
if (addWorker(command, true))
return;
c = ctl.get();
}
// 提交到队列中,offer返回true/false
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果队列满了,则开启最大线程,开启不冷,则拒绝
else if (!addWorker(command, false))
reject(command);
}
- addWorker详解,addWorker执行线程
在threadPoolExecutor中,定义了一个Worker类,用于表示执行线程对象。其中该Worker类继承了AQS也具备了AQS的同步性质。
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;
/** 执行线程 */
final Thread thread;
/** 线程开始执行的任务 */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
/** * 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);
}
// 运行线程,执行任务,可以通过runworker方法获得对应任务进行执行,核心线程会一直执行run方法,等待队列有任务进行消费
public void run() {
runWorker(this);
}
}
先执行firstRunnable,如果没有则去获得getTask从堵塞队列中获得对应任务进行执行。
runWorker
// 如果初始化任务不为空,则执行,如果为空,则去队列里面拿任务
while (task != null || (task = getTask()) != null) {
在getTask方法中会通过workQueue.take进行获得数据,如果该线程为非核心线程则通过poll方法进行UNSAFE.park(false, nanos);
// timed为keepAlive时间
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
如果getTask为空,则表示为非核心线程,则会在runworker执行processWorkerExit退出。
2. ExecutorSerivce
在抽象类中executorService提供Runnable和Callable的方式,可以提供其中同步任务和异步任务,如果是同步任务,则返回的Future为空。
三、理解ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor是一个可以在指定一定延迟时间后或者定时进行任务调度执行的线程池,实现了ScheduledExecutorService,继承了ThreadPoolExecutor。
public static void main(String[] args) {
ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(5);
// Task 实现Runnable
Task task = new Task("任务");
System.out.println("Created : " + task.getName());
executor.schedule(task, 2, TimeUnit.SECONDS);// 只执行一次
// executor.scheduleWithFixedDelay(task, 0, 2, TimeUnit.SECONDS); //任务+延迟
// executor.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);
}
该线程池重点在于接口参数的时候,会将延伸时间,或者是固定频率传递给参数中,传递过去会封装RunnableScheduledFuture对象,该对象中具有延时参数的性质。
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
private long triggerTime(long delay, TimeUnit unit) {
return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
}
long triggerTime(long delay) {
return now() +
((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
}
ScheduledFutureTask(Runnable r, V result, long ns) {
super(r, result);
this.time = ns;
// 周期
this.period = 0;
this.sequenceNumber = sequencer.getAndIncrement();
}
然后通过addWorker方法将该类型放置到DelayedWorkQueue队列,该线程池会直接把任务放到队列中,而不是使用核心线程进行执行。会开启一个线程进行执行,执行的时候会去获得getTask方法,这个时候队列会执行对应的take方法,DelayedWorkQueue本质上是一个最小堆,时间快执行的会放在堆头,那么获得该对象后,会判断该delay时间是否小于0,小于的话,则执行,不小于的话,则进行condition.awaitNanos->LockSupport.parkNanos->NUSAFE.park(false,nanos) 将当前线程放在等待队列中,等待毫米执行完成,从而重新获得最小的节点。
三、理解UNSAFE.park
UNSAFE底层是会去执行jvm底层c++代码,会去操作操作系统,在操作系统中有一个调度执行器,线程其实也是一个对象,会被封装成task_struct对象。
该task_struct会被放在调度任务中,等待cpu的调度,执行完一段时间后,则会切换不同的线程进行执行,也就是取决于os的调度算法。
如果执行了park操作,也就是将线程进行挂起,在synchronize中会将线程会在monitor的等待队列中,会将task_struct从调度任务集合中取出来放在对应的等待队列中,这个就是将线程进行挂起,而执行unpark则将线程进行放回到调度任务集合中。
四、理解线程中断机制
在任务执行的时候是开启一个线程进行执行,与主流程是不同线程,这样如果要停止任务的话,可以通过线程中断的机制来停止线程执行,但是jvm并没有操作系统的中断机制,而是采用代码介入的方式来停止,比如如下机制可以停止线程:
/** * 通过一个volatile变量实现 volatile具有可见性和有序性 */
static volatile boolean isStop = false;
public static void m1()
{
new Thread(() -> {
while(true)
{
if(isStop)
{
System.out.println("-----isStop = true,程序结束。");
break;
}
System.out.println("------hello isStop");
}
},"t1").start();
//暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {
e.printStackTrace(); }
new Thread(() -> {
isStop = true;
},"t2").start();
}
上述代码是采用外部变量来控制,通过在任务执行的时候介入代码判断来判断是否外部用人中断了执行,java的线程也就是提供了这个中断标志。
线程中断并不会使线程立即退出,而是给线程发送一个通知,提醒它退出。有三个方法与线程中断有关:
实例方法 interrupt () :它通知目标线程中断,也就是给目标线程设置中断标志位。
实例方法 isInterrupted () :它通过检查中断标志位来判断目标线程是否被中断。
静态方法 interrupted () :它和 isInterrupted 方法类似,也能判断目标线程是否中断,同时它还会清除中断标志位
isInterrupted 和 interrupted 在于 返回值是相同的,但是interrupted会将中断设置为false,也就是说下次执行interrupted会返回false;
1. 理解java可中断操作
- 比如在执行Thread.sleep的时候就有一个受检查类型InterruptedException
class InterruptedException extends Exception
该异常是需要进行catch的异常,也就是在sleep以及一些堵塞的方法的时候,线程可以中断,这样线程会抛出一个InterruptedException,但是InterruptedException会自动执行interrupted(),会将中断重新设置为false,因此需要在异常的时候处理其中中断逻辑。
- 在blockingQueue的时候有些操作,比如take是可中断的
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
这里使用了ReentrantLock可中断锁,而synchronized 不是可中断锁。 因此这里的take提供了可中断形式,那我们看看这里的中断是如何进行的,
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
// parkAndCheckInterrupt 返回true则表示该线程被中断了,因此会抛出线程中断的异常
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
在这里我们看到parkAndCheckInterrupt会去执行park方法进行堵塞线程。
park方法,如果线程被中断的也就是调用interrupt方法的时候,park方法会被返回
因此在parkAndCheckInterrupt方法的时候,会有一个return 语句。
五、理解FutureTask(可被取消、可异步获得结果的执行任务)
在任务执行的接口中有常见的Runnable,但是这个只是执行run方法,不能获得任务的返回数据,因此提供了Callable以及Future接口。
Future 是个接口,它可以对具体的 Runnable 或者 Callable 任务进行取消、判断任务是否已取消、查询任务是否完成、获取任务结果。注意两个 get 方法都会阻塞当前调用 get 的线程,直到返回结果或者超时才会唤醒当前的线程。
public interface Future<V> {
//取消任务的执行,任务已经完成或者已经被取消时,调用此方法,会返回false
boolean cancel(boolean mayInterruptIfRunning);
//判断任务是否被取消
boolean isCancelled();
//判断任务是否完成(正常完成、异常以及被取消,都将返回true)
boolean isDone();
//阻塞地获取任务执行的结果
V get() throws InterruptedException, ExecutionException;
//在一定时间内,阻塞地获取任务执行的结果
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
FutureTask 是真正工作的处理类,FutureTask 实现了 RunnableFuture 接口(继承Runnable接口和Future接口,因此具有二个接口的特性),而 RunnableFuture 继承了 Runnable 与 Future 接口,因此 FutureTask 既可以作为一个Runnable 被 Thread 执行,也可以获取到 Future 异步计算的结果。也就是说如果需要声明一个异步任务的话,则可以定义一个FutureTask进行放线程池执行,然后通过futureTask对象获得对应的执行结果
如果只是一个单纯的Runnable的任务的话,没有返回值那是不能用到Future异步接口的数据的,因此FutureTask提供如下二个接口
常用方法
public boolean isCancelled() 如果此任务在正常完成之前取消,则返回 true 。
public boolean isDone() 返回true如果任务已完成。
public V get() 等待计算完成,然后检索其结果。
public V get(long timeout, TimeUnit unit)如果需要等待最多在给定的时间计算完成,然后检索其结果(如果可用)。
public boolean cancel(boolean mayInterruptIfRunning)尝试取消执行此任务。
protected void set(V v)将此未来的结果设置为给定值,除非此未来已被设置或已被取消。
其中get都是堵塞性的方法,在cancel中底层也是使用的interrupt方法,如果有cancel的需求的话,这样需要在对应的task任务中编写中断对应的代码。
- ScheduledFutureTask.cancel方法说明
public boolean cancel(boolean mayInterruptIfRunning) {
boolean cancelled = super.cancel(mayInterruptIfRunning);
if (cancelled && removeOnCancel && heapIndex >= 0)
// 去对应的workQueue中移除该对应任务
remove(this);
return cancelled;
}
在延时任务中,因为任务会执行放在workQueue进行循环执行,因此会在调用futureTask.cancel后还会去移除对应的任务数据
FutureTask.cancel方法如下所示:
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try {
// in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally {
// final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
重点在于t.interrupt(),也就是取消的时候会去调用中断方式,如果有取消的需求,则代码中应该去处理中断。
10. 基础知识
- UNSAFE.park() 堵塞的方法是支持Thread.interrupt()中断,如中断则会返回
文章评论