2019独角兽企业重金招聘Python工程师标准>>>

在文章FutureTask源码分析中简单说明了FutureTask中使用Treiber堆栈来保存等待结果的线程,本文将详细分析其原理。

Treiber堆使用CAS操作来实现节点的入栈和出栈,由于CAS操作只是保证操作的原子性,多线程并发时,其并不能保证可见性,因此必须依赖volatile来保证写入的可见性。这样就可以不必使用加锁来实现对共享数据结构的访问。下面先看一个实现Treiber堆的例子:

public class TreiberStack<E> {AtomicReference<Node<E>> head = new AtomicReference<Node<E>>();public void push(E item) {//总是在堆顶加入新节点Node<E> node= new Node<E>(item);Node<E> old;do {old = head.get();node.next = old;} while (!head.compareAndSet(old, node));}public E pop() {//总是从堆顶移除Node<E> old;Node<E> node;do {old = head.get();if (old == null) return null;node = old.next;} while (!head.compareAndSet(old,node));return old.item;}private static class Node<E>{public final  E  item;public Node<E>   next;public Node(E item){this.item=item;}}

FutureTask中WaitNode 作为节点,并将当前线程保存在其中,而且将栈顶元素保存在waiters中。

入栈

首先看入栈操作,当调用FutureTask的get方法时,若任务未完成则会将当前等待结果的线程加入到等待队列,并挂起当前线程。

    private int awaitDone(boolean timed, long nanos)throws InterruptedException {final long deadline = timed ? System.nanoTime() + nanos : 0L;WaitNode q = null;boolean queued = false;for (;;) {//当前线程是否已中断,该方法会清除线程状态,也就是说第一次调用返回true,//并且没有再次被中断时,第二次调用将返回falseif (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) //表示任务马上完成,不必进入等待队列Thread.yield();else if (q == null)//此时s只可能为NEWq = 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);//等待指定时间间隔后挂起}elseLockSupport.park(this);//挂起线程}}

由源码可知,当前线程未被中断时,使用CAS操作加入当前等待节点q,通过将q设为新的栈顶元素,即waiters,同时修改q.next指针指向上一次的waiters。这里使用自旋操作来保证操作一定成功。

看下面例子:

public class FutureTaskStackTest {private static FutureTask<Integer> ft=new FutureTask<Integer>(new ComputeTask(0,"task"));public static void main(String[] args) throws InterruptedException,ExecutionException{ExecutorService   executor=Executors.newSingleThreadExecutor();executor.submit(ft);executor.shutdown();for(int i=0;i<5;i++){System.out.println("for loop "+i);System.out.println(Thread.currentThread()+":"+ ft.get());}}private static class ComputeTask implements Callable<Integer>{private Integer result ;  private String  taskName ; public ComputeTask(Integer init, String taskName){  result = init;  this.taskName = taskName;  }  @Overridepublic Integer call() throws Exception {for (int i = 0; i < 100; i++) {  result =+ i;  } Thread.sleep(5000);System.out.println(taskName+" finish!");  return result;}}
}

输出:

for loop 0
task finish!
Thread[main,5,main]:99
for loop 1
Thread[main,5,main]:99
for loop 2
Thread[main,5,main]:99
for loop 3
Thread[main,5,main]:99
for loop 4
Thread[main,5,main]:99

ComputeTask将sleep 5000ms才会完成任务,主线程中循环调用5次future.get()。那么等待队列中会加入5个节点吗?实际不是,只会加入一个,当加入一个时,当前main线程会被挂起,即输出“for loop 0”之后被挂起,直到任务完成被唤醒。这就说明同一个线程中调用多次future.get()和调用一次在FutureTask中都将只会加入一个节点,当线程被唤醒时,future.get()将不会被阻塞。

那么使用如下例子:

public class FutureTaskStackTset {private static FutureTask<Integer> ft=new FutureTask<Integer>(new ComputeTask(0,"task"));public static void main(String[] args) throws InterruptedException,ExecutionException{ExecutorService   executor=Executors.newSingleThreadExecutor();executor.submit(ft);executor.shutdown();MyThread thread1=new MyThread();thread1.setName("thread1");MyThread thread2=new MyThread();thread2.setName("thread2");thread1.start();thread2.start();System.out.println(Thread.currentThread().getName()+" : "+ ft.get());}private static class ComputeTask implements Callable<Integer>{private Integer result ;  private String  taskName ; public ComputeTask(Integer init, String taskName){  result = init;  this.taskName = taskName;  }  @Overridepublic Integer call() throws Exception {for (int i = 0; i < 100; i++) {  result =+ i;  } Thread.sleep(5000);System.out.println(taskName+" finish!");  return result;}}private static class MyThread extends Thread{public void run() {try {System.out.println(this.getName()+" : "+ ft.get());} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}}
}

输出:

task finish!
main : 99
thread1 : 99
thread2 : 99

在这个例子中,在不同的线程中调用future.get(),因此Treiber堆中会加入3个等待节点。这里main线程中的future.get()必须在启动其他线程之后调用,否则,main线程被阻塞,那么子线程就不会被启动,自然也不会加入等待队列。

移除

awaitDone实现可知,若当前线程被中断,FutureTask将会清理等待队列,移除已经被中断线程的节点。

private void removeWaiter(WaitNode node) {if (node != null) {node.thread = null;retry:for (;;) {for (WaitNode pred = null, q = waiters, s; q != null; q = s) {s = q.next;if (q.thread != null)pred = q;else if (pred != null) {//移除thread为null的节点pred.next = s;if (pred.thread == null) // check for racecontinue retry;}//q和pred的thread均为null,将s设为新的栈顶元素,即waiterselse if (!UNSAFE.compareAndSwapObject(this, waitersOffset,q, s))continue retry;//失败则重新进入循环}break;}}}

removeWaiter首先将要移除节点的thread变量置为null,这一步很关键,因为后续移除等待节点就是根据thread是否为null来实现。下面分别分析几种可能:

1 waiters即为要移除的元素

从代码分析,q指向waiters的节点,s=q->next,则s指向T1,此时q.thread为null,pred也为null,进入else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s)),将s设为waiters,成功移除为null的节点。

2 thread为null元素位于堆栈中间

当找到q.thread为null的元素时,pred将指向T1,而S则指向T2,那么程序将会进入else if (pred != null)的代码块,将pred的next指针指向S,则成功将q移除。

完全有可能出现thread为null的元素位于中间甚至末尾的情况,当多线程调用get()方法时,CAS保证同时只能有一个线程将节点加入等待队列。失败的线程将继续进行自旋操作,直到成功。同时,上文已提过,相同线程多次调用get也只会加入一个节点到等待队列,因此removeWaiter一次调用实际只会移除一个节点。

removeWaiter操作的作用在于移除无效节点,避免造成垃圾累积,当堆栈中节点较多,removeWaiter操作会很慢。通常情况下,不会有太多线程同时等待一个任务的结果。

出栈

当任务执行完成后,将在 finishCompletion()中唤醒所有节点,此时所有线程都可以拿到结果。

private void finishCompletion() {// assert state > COMPLETING;for (WaitNode q; (q = waiters) != null;) {if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {for (;;) {Thread t = q.thread;if (t != null) {q.thread = null;LockSupport.unpark(t);}WaitNode next = q.next;if (next == null)break;q.next = null; // unlink to help gcq = next;}break;}}done();callable = null;        // to reduce footprint}

finishCompletion()实现即可知,首先使用CAS操作将waiters置为null,然后从栈顶到栈底唤醒所有等待节点,并将节点的thread和next置空,这有助于GC回收内存。

欢迎指出本文有误的地方,转载请注明原文出处https://my.oschina.net/7001/blog/875714

转载于:https://my.oschina.net/7001/blog/875714

FutureTask中Treiber堆的实现相关推荐

  1. Java基础-Java中的堆内存和离堆内存机制

    Java基础-Java中的堆内存和离堆内存机制 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 转载于:https://www.cnblogs.com/yinzhengjie/p/9 ...

  2. python变量存储 堆与栈内存内存_浅析JS中的堆内存与栈内存

    最近跟着组里的大佬面试碰到这么一个问题, Q:说说var.let.const的区别 A:balabalabalabla... Q:const定义的值能改么? A:你逗我?不能吧 不知道各位看官怎么想? ...

  3. java中的堆、栈、方法区等比较

    • 堆.栈.方法区 1. java中的栈(stack)和堆(heap)是java在内存(ram)中存放数据的地方 2. 堆区 存储的全部是对象,每个对象都包含一个与之对应的class的信息.(clas ...

  4. Java中的堆和栈的区别

    当一个人开始学习Java或者其他编程语言的时候,会接触到堆和栈,由于一开始没有明确清晰的说明解释,很多人会产生很多疑问,什么是堆,什么是栈,堆和栈有什么区别?更糟糕的是,Java中存在栈这样一个后进先 ...

  5. Java中的堆分配参数总结《对Java的分析总结》(二)

    <对Java的分析总结>-Java中的堆分配参数总结 header 配制说明 -Xms: 设置Java应用程序启动时的初始堆大小 -Xmx: 设置Java应用程序能获得的最大堆大小 -Xs ...

  6. (十一)C语言中内存堆和栈的区别

    在计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到.但对于很多的初学着来说,堆栈是一个很模糊的概念. 堆栈:一种数据结构.一个在程序运行时用于存放的地方,这可能是很多初学者的认 ...

  7. C++中栈区 堆区 常量区

    原文出自:http://baike.baidu.com/view/536145.htm C++中栈区 堆区 常量区(由一道面试题目而学习) 2009-04-28 21:01 #include<i ...

  8. Jvm(29),理解升级----C语言中的堆和栈的区别 (可以借鉴)

    假如你现在还在为自己的技术担忧,假如你现在想提升自己的工资,假如你想在职场上获得更多的话语权,假如你想顺利的度过35岁这个魔咒,假如你想体验BAT的工作环境,那么现在请我们一起开启提升技术之旅吧,详情 ...

  9. 关于.NET内存中的堆和栈

    http://www.cnblogs.com/zoupeiyang/archive/2011/07/20/2112163.html 今天中午李建忠老师发了一条关于class性能好还是struct性能好 ...

最新文章

  1. httpclient工具类,post请求发送json字符串参数,中文乱码处理
  2. PS想象的力量无限大,设计师的脑洞无限大!
  3. 评《认知红利》 谢春霖著
  4. Android SqliteManager 源码
  5. dotNET Core WebAPI 统一处理(返回值、参数验证、异常)
  6. cad完全卸载教程_CAD室内设计中厨房布置实例
  7. UPS不间断电源的种类有哪些 常见的3类UPS电源
  8. iOS 面试题分析(二)
  9. Atitit 知识图谱解决方案:提供完整知识体系架构的搜索与知识结果overview
  10. 数字图像处理——图像锐化
  11. echarts考勤图表
  12. request method ‘DELETE‘ not supported报错处理
  13. 对国内基金行业的一些思考 【投资干货】
  14. 【基于JAVA的旅游路线推荐系统-哔哩哔哩】 https://b23.tv/4STx5NI
  15. java什么是布尔型_Java新职篇:是什么是布尔型?
  16. 电影《终结者2018》
  17. 自从学会了用python解析视频,都不用去找下载按钮在哪了,是真的方便
  18. 企业微信可以取消实名认证吗?如何操作?
  19. linux--- 连接数据库
  20. yahoo地图 vs Google地图

热门文章

  1. smarty的简单分页
  2. Linux下怎么诊断网站性能异常
  3. Linux服务器网络开发模型
  4. 用memcache.php监测memcache的状况
  5. NEON在Android中的使用举例
  6. springboot取yml中的值_SpringBoot 中从yml配置文件中读取常用的参数值
  7. arima模型matlab代码_PSTR面板平滑转换模型简介(附Matlab代码分享)
  8. 计算机组装cpu用哪种好,教你电脑处理器哪款好
  9. bpython ipython_安装ipython后命令找不到ipython bpython -bash: *python: command not found
  10. Java项目:养老院管理系统(java+SSM+JSP+Easyui+maven+mysql)