预备知识:Java 线程挂起的常用方式有以下几种

  1. Thread.sleep(long millis):这个方法可以让线程挂起一段时间,并释放 CPU 时间片,等待一段时间后自动恢复执行。这种方式可以用来实现简单的定时器功能,但如果不恰当使用会影响系统性能。
  2. Object.wait()Object.notify()Object.notifyAll():这是一种通过等待某个条件的发生来挂起线程的方式。wait() 方法会让线程等待,直到其他线程调用了 notify()notifyAll() 方法来通知它。这种方式需要使用 synchronized 或者 ReentrantLock 等同步机制来保证线程之间的协作和通信。
  3. LockSupport.park()LockSupport.unpark(Thread thread):这两个方法可以让线程挂起和恢复。park() 方法会使当前线程挂起,直到其他线程调用了 unpark(Thread thread) 方法来唤醒它。这种方式比较灵活,可以根据需要控制线程的挂起和恢复。

一:示例-引入主题

public class FutureTaskDemo {
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<>(new Callable() {
@Override
public Object call() throws Exception {
System.out.println("异步线程执行");
Thread.sleep(3000);//模拟线程执行任务需要3秒
return "ok";
}
});
Thread t1 = new Thread(futureTask, "线程一");
t1.start(); try {
//关键代码
String s = futureTask.get(2, TimeUnit.SECONDS); //最大等待线程2秒
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}

二:进入futureTask.get(2, TimeUnit.SECONDS);

  public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING) //重点awaitDone,即完成了最大等待,依然没有结果就抛出异常逻辑
throw new TimeoutException();
return report(s);
}

​ awaitDone返回线程任务执行状态,即小于等于COMPLETING(任务正在运行,等待完成)抛出异常TimeoutException

三:进入(awaitDone(true, unit.toNanos(timeout)))原理分析

private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
} int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}

3.1 总体解读awaitDone

利用自旋(for (;)的方式 ,检查state(任务状态)与waitNode(维护等待的线程),

第一步:首先检查if (Thread.interrupted()) 线程是否被打断(LockSupport.parkNanos挂起的线程被打断不抛出异常),

第二步:判断任务状态与waitNode是否入队+确定最大等待时间

​ 若已完成(if (s > COMPLETING))返回任务状态

​ 若已完成(if (s == COMPLETING))-->表示正在完成,但尚未完成。则让出 CPU,进入就绪状态,等待其他线程的执行

​ 若if (q == null)==>创建等待等待节点

​ 若if (!queued)==>表示上一步创建的节点没有和当前线程绑定,故绑定

​ 最后else if (timed)与else,判断最大等待时间

static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
private static final int NEW          = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
state可能转换的过程
1.NEW -> COMPLETING -> NORMAL (成功完成)
2.NEW -> COMPLETING -> EXCEPTIONAL (异常)
3.NEW -> CANCELLED (任务被取消)
4.NEW -> INTERRUPTING -> INTERRUPTED(任务被打断)

3.2 关键代码

LockSupport.park(this, nanos) ==内部实现==> UNSAFE.park(false, nanos)();

​ 即让当前线程堵塞直至指定的时间(nanos),该方法同Thread.sleep()一样不会释放持有的对象锁,但不同的是Thread.sleep会被打断(interrupted)并抛出异常,而LockSupport.park被打断不会抛出异常,故在自旋时(for (;)需判断if (Thread.interrupted())线程是否被打断(手动抛出异常)。

四:线程运行时state的变化轨迹

4.1:新建时利用构造器设置state=NEW

 public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // 赋值状态
}

4.2: 线程运行时state可能变化轨迹

public void run() {
..........防止多次运行stat()方法..............
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex); //异常轨迹---> 见下分析
}
if (ran)
set(result); // 正常轨迹--->见下分析
}
} finally {
runner = null;
//----最后结束---防止线程被打断
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}

异常轨迹setException(ex)

protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
//轨迹变化 2.NEW -> COMPLETING -> EXCEPTIONAL (异常)
}
//否则1: 3.NEW -> CANCELLED (任务被取消)
//否则2: 4.NEW -> INTERRUPTING -> INTERRUPTED(任务被打断)
}

正常轨迹 set(result);

 1.NEW -> COMPLETING -> NORMAL (成功完成)

源码解读之FutureTask如何实现最大等待时间的更多相关文章

  1. Future、FutureTask实现原理浅析(源码解读)

    前言 最近一直在看JUC下面的一些东西,发现很多东西都是以前用过,但是真是到原理层面自己还是很欠缺. 刚好趁这段时间不太忙,回来了便一点点学习总结. 前言 最近一直在看JUC下面的一些东西,发现很多东 ...

  2. ScheduledThreadPoolExecutor源码解读

    1. 背景 在之前的博文--ThreadPoolExecutor源码解读已经对ThreadPoolExecutor的实现原理与源码进行了分析.ScheduledExecutorService也是我们在 ...

  3. ThreadPoolExecutor源码解读

    1. 背景与简介 在Java中异步任务的处理,我们通常会使用Executor框架,而ThreadPoolExecutor是JUC为我们提供的线程池实现. 线程池的优点在于规避线程的频繁创建,对线程资源 ...

  4. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  5. SDWebImage源码解读 之 NSData+ImageContentType

    第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...

  6. SDWebImage源码解读 之 UIImage+GIF

    第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...

  7. SDWebImage源码解读 之 SDWebImageCompat

    第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...

  8. SDWebImage源码解读_之SDWebImageDecoder

    第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...

  9. SDWebImage源码解读之SDWebImageCache(上)

    第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...

  10. SDWebImage源码解读之SDWebImageCache(下)

    第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...

随机推荐

  1. 微信小程序开发常见问题

    1.不同页面之间的传值方式 通过URL问号传值 当前页面 wx.navigateTo({ url: '/pages/aaa/aaa?/userName=norma' }) 2. 另一个页面通过opti ...

  2. 类和动态内存分配的课后习题(C++ prime plus)

    第一题 1. 对于下面的类声明: class Cow { char name[20]; char *hobby; double weight; public: Cow(); Cow(const cha ...

  3. P1138 第 k 小整数

    P1138 第 k 小整数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) dalao 勿喷,适合新手 思路就是排序加查重,最后判断第k小数.思路十分简单. 刚开始我一直想二维循环查 ...

  4. 面向对象ooDay9

    精华笔记: 多态:多种形态 同一个对象被造型为不同的类型时,有不同的功能-------所有对象都是多态的(明天总结详细讲) 对象的多态:水.我.你...... 同一类型的引用在指向不同的对象时,有不同 ...

  5. 【cs231n】knn作业笔记

    完成了assignment-1中knn相关内容的作业,记录一下遇到的知识点和问题 knn.ipynb的内容大致包括: 1.数据集的建立 主要是通过切片函数,如下图选取前5000张图片和其标记作为训练数 ...

  6. C# 高精度定时器

    https://blog.gkarch.com/2015/09/high-resolution-timer.html https://www.cnblogs.com/samgk/articles/57 ...

  7. C++常见报错信息和原因的对应关系

    1. 无法找到 xxx.dll 没有把动态链接库和exe放在一个文件夹下 2. 不允许使用不完整的类型 指的是忘了加头文件 3. link err .无法解析的外部符号 指的是 lib 库没有配置对 ...

  8. CV入坑

    https://www.cnblogs.com/fldev/p/14360149.html

  9. Shell脚本实现模拟并发及并发数控制

    #!/bin/bash #by inmoonlight@163.com #下面的代码控制并发数.其实是利用令牌原理实现 #一个线程要运行,首先要拿到令牌在该代码中即read一行数据,读取不到就会暂停, ...

  10. 备份linux系统日志脚本

    #!/bin/bash#script_name bkup_log.sh#7 0 * * 1 cd /home/tools/;./bkup_log.sh >& /dev/nullproce ...