预备知识: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. 解决vs未能找到导入的项目

    本文解决的问题: VS显示"vs未找到导入XXX.vcxproj 项目,请确认 import 声明中的路径正确". 找到 修改成非只读,打开后删除所有诸如这样的 Import 标签 ...

  2. 【python】第一模块 步骤五 第二课、Python多线程

    第二课.Python多线程 一.课程介绍 1.1 课程概要 章节概要 进程.线程与并发 对多核的利用 实现一个线程 线程之间的通信 线程的调度和优化 1.2 为什么要学习多线程 (线程)使用场景 快速 ...

  3. Android studio 使用dialog提示信息

    package com.example.androidtest2; import androidx.appcompat.app.AlertDialog;import androidx.appcompa ...

  4. create-react-app react 使用dll抽离公共库,大幅缩减项目体积,及项目打包速度

    1.安装依赖(clean-webpack-plugin.add-asset-html-webpack-plugin.webpack-cli) yarn add clean-webpack-plugin ...

  5. 5-3 掌握 egg.js + 云 mongodb

    1 egg.js 1.1 初始化 初始化和项目启动方法 # 初始化 $ mkdir egg-example && cd egg-example $ npm init egg --typ ...

  6. echarts 图表 tooltip提示框,formatter自定义

    自定义图表提示框样式, 自定义原因:series中有多个数据样式,那么提示框会展示多条. tooltip: { formatter(params) { let circle = `<span s ...

  7. docker安装nginx挂载启动

    docker pull nginx 命令安装 查找 Docker Hub 上的 nginx 镜像: docker search nginx 这里我们拉取官方的镜像 docker pull nginx ...

  8. redis和memcached区别总结

    1.数据结构:redis支持五种数据结构(字符串,列表,哈希,集合,有序集合),并支持很多相   关计算,例如排序,阻塞等等.支持阻塞队列,锁,线程通信等功能.而memcached只支持kv简单存储 ...

  9. Python笔记(5)——if 语句一:条件测试(Python编程:从入门到实践)

    每条if语句的核心都是一个值为True或False的表达式.Python根据条件测试的值为True还是False来决定是否执行if语句中的代码.如果条件测试的值为True,Python就执行紧跟在if ...

  10. 『教程』mariadb的主从复制

    一.MariaDB简介 MariaDB数据库的主从复制方案,是其自带的功能,并且主从复制并不是复制磁盘上的数据库文件,而是通过binlog日志复制到需要同步的从服务器上. MariaDB数据库支持单向 ...