当前位置: 首页 > 编程日记 > 正文

FutureTask中Treiber堆的实现

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

在文章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即为要移除的元素

181233_28Cq_2663573.png

从代码分析,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元素位于堆栈中间

181842_65vQ_2663573.png

当找到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

相关文章:

软件开发过程中遇到的问题

今天早晨去石家庄河北电力工程监理有限公司&#xff0c;回来后就在整理这篇图文&#xff0c;结果还是过了12:00。这是针对昨天图文“Software Development Problem”的翻译以及自己的一些理解&#xff0c;分享给大家。 其实&#xff0c;看看这些东西蛮好的&#xff0c;最起码能…

Java基础学习总结(9)——this关键字

2019独角兽企业重金招聘Python工程师标准>>> 一、this关键字 this是一个引用&#xff0c;它指向自身的这个对象。 看内存分析图&#xff1a; 假设我们在堆内存new了一个对象&#xff0c;在这个对象里面你想象着他有一个引用this&#xff0c;this指向这个对象自己&am…

转贴:雅虎公司C#笔试题,看看你能解答多少

这是刚在在网上看到了&#xff0c;觉得这些题目出得真不错&#xff0c;能考出一个程序员的基本功。所以先发在这里&#xff0c;做个备份&#xff0c;以后慢慢来做&#xff08;偶好像只能免强及格哦&#xff0c;呵呵&#xff0c;关于网络的题目太多了&#xff0c;偶不太熟啊&…

亿级PV请求的三种负载均衡技术

在互联网不断渗透到生活中的今天&#xff0c;各种各样的网络服务存在在我们身边&#xff0c;他们的访问流量也是大得惊人。一个大型网站&#xff08;百万PV以上&#xff09;想要正常访问&#xff0c;单单靠一台服务器是不可能提供稳定服务的。这时候就需要用负载均衡技术将海量…

The Six Best Practices(1~3)

前几期的图文我们介绍了软件工程中常见的问题&#xff0c;分析了产生这些问题的根本原因&#xff0c;引出了软件工程中的六个最佳经验。今天我们具体介绍这些最佳经验的内容。

Java项目命名规范

2019独角兽企业重金招聘Python工程师标准>>> 一、命名规范 1、 项目名全部小写 2、 包名全部小写 3、 类名首字母大写&#xff0c;如果类名由多个单词组成&#xff0c;每个单词的首字母都要大写。 如&#xff1a;public class MyFirstClass{} 4、 变量名、方法名首字…

一道数据结构算法题

现有一个链表&#xff0c;证明如果存在环&#xff0c;则&#xff1a;使用两个指针同时前进但步长不一样&#xff0c;则能够在有限步之后能够相逢。题目的意思是我归纳出来的&#xff0c;我的解题思路是这样的&#xff1a;能够相下逢的意思是&#xff1a;在走了x步以后&#xff…

win7系统下载 ghost win7 Sp1 32位纯净3月版

win7系统下载 ghost win7 Sp1 32位纯净3月版软件名称: Ghost Win7 Sp1 32位纯净3月版软件语言: 简体中文软件大小: 3.81大小: GB发布日期: 2017-03-21文件名称: ZJY_Ghost_win 7_X86_CJ201703.GHOM D 5: C21A7A17D8C2568A05844C5…

The Six Best Practices(4~6)

昨天图文介绍了The Six Best Practices的前三个经验&#xff1a;迭代化开发、需求管理、基于构件的体系构架&#xff0c;今天介绍后面的三个经验&#xff1a;可视化建模、持续的质量验证和变更管理。

代码生成器前戏 之 数据库元数据

总结&#xff1a;代码生成器大致有两种方式&#xff0c;1.根据po 生成 表 结构 等系列类&#xff0c;如 Hibernate自动生成 2.根据表生成 po 等系列类。其实实际 开发时候 多半还是 先设计好表&#xff0c;然后生成 的情况多。 元数据&#xff1a;描述数据的数据&#xff0c;就…

有感于框架设计难,实施框架更难!

很久没有写了&#xff0c;不能怪没有时间&#xff0c;只是自己太懒。这两天休息&#xff0c;有时间重新回顾一下项目的设计&#xff0c;从目前的开发情况看&#xff0c;最早设计的一些编程框架&#xff0c;并没有很好的遵守&#xff0c;看上去比较零乱&#xff0c;这个可能由于…

六个最佳的软件工程实践(基于构件的体系结构、可视化建模)

昨天图文介绍了迭代化开发和需求管理&#xff0c;今天我们介绍基于构件的体系结构和可视化建模。基于构件的体系结构是软件开发中最体现创造力的地方&#xff0c;可以通过“修建桥梁”来理解为什么要贯彻基于构件的体系结构以指导我们每次开发的演进增量过程。可视化建模是为了…

通讯组基本管理任务一

经常收发邮件的人都有同感&#xff1a;很多时候都需要向一组人发送邮件。如果一封封地发送&#xff0c;不仅耽误时间&#xff0c;而且很容易出错&#xff0c;将邮件发送给不应该接受邮件的收件人。Exchange Server 2010为了方便用户使用&#xff0c;通过“通讯组”和“地址列表…

无人驾驶相关数据集

普林斯顿大学人工智能自动驾驶汽车项目&#xff1a; 代码V1&#xff1a;http://deepdriving.cs.princeton.edu/DeepDrivingCode_v1.zip 代码V2&#xff1a; http://deepdriving.cs.princeton.edu/DeepDrivingCode_v2.zip 训练集&#xff08;50G&#xff09;&#xff1a;http://…

六个最佳的软件工程实践(持续的质量验证、变更管理)

昨天图文介绍了基于构件的体系结构以及可视化建模&#xff0c;今天我们介绍六个最佳工程实践的最后两个&#xff0c;持续的质量验证以及变更管理。持续的质量验证是伴随迭代化开发而进行的不断验证&#xff0c;且每次迭代的测试集合都是不断递增的。质量验证不仅从功能方面要满…

css层叠样式表(一)

今天研究了下css。这东东入门不算难。可是想写出好的样式就得有很深的功底了。按照老大给网址&#xff0c;12天学会网页设计。做下总结吧。css通过div&#xff08;层&#xff09;来定位&#xff0c;通过层的margin,padding,border等属性来控制板块的间距。常用的模型是盒状模型…

aspx页面使用ajax遇到try catch中使用Response.End()报错

1、使用Ajax接收数据&#xff0c;在返回Response.Write()后应该调用Response.End()才能将数据写入到调用的页面,才能被jQuery的回调函数获取到返回的JSON数据 2、在try--catch里面不能用Response.End()&#xff0c;否则会报错&#xff1a;由于代码已经过优化或者本机框架位于调…

GO是更好的编程语言吗?

引言 团队有项目考虑用GO重写&#xff0c;所以花了些时间调研GO。 第一次接触GO是2年前&#xff0c;17年3月份&#xff0c;全职钻研一周&#xff0c;彼时C中毒太深&#xff0c;内心排斥其他编程语言&#xff0c;看其他语法总觉得有点怪&#xff0c;而且有“C/C能做任何事&#…

定位DIV滚动条

如果由于table中有一个下拉框&#xff0c;还有一个treeview时&#xff0c;treeview的所有节点都是取于下拉框的下拉选项来的&#xff0c;所以在第一定位这之后&#xff0c;当选择其他下拉框中其他的选项时&#xff0c;DIV的scrollTop值是会一直保存前一个步聚DIV滚动条所在的位…

JS作用域相关知识(#精)

在学习《你不知道的JS》一书中&#xff0c;特将作用域相关知识在此分享一下: #说到作用域&#xff0c;就不得不提到LHS查询和RHS查询: 1)如果查询目的是对变量进行赋值&#xff0c;则使用LHS查询 2)如果查询目的是获取变量的值&#xff0c;则使用RHS查询 作用域的查询都会从当前…

RUP within the context of the Six Best Practices

前面几期图文介绍了软件工程中常见的问题&#xff0c;以及找到它们的根本原因&#xff0c;提出了在软件工程实践中总结出来的六个最佳工程实践。迭代化开发、需求管理、基于构件的体系结构、可视化建模、持续的质量验证、变更管理。今天我们介绍Rational公司的RUP&#xff0c;看…

排序算法java版,速度排行:冒泡排序、简单选择排序、直接插入排序、折半插入排序、希尔排序、堆排序、归并排序、快速排序...

先推荐一篇关于排序算法的文章&#xff1a;http://www.cppblog.com/guogangj/archive/2009/11/13/100876.html 本文思路部分来源于上篇文章&#xff0c;但测得的结果似乎不大相同&#xff0c;不知是因为java的缘故还是因为我算法的缘故&#xff0c;欢迎拍砖。 复习排序&#xf…

项目存档管理规范

项目存档管理规范 在我们开发过很多个项目之后&#xff0c;每个项目都会累积下很多源码、文档等&#xff0c;查找和整理起来很不方便&#xff0c;如果我们又要同时工作于多个项目的话&#xff0c;情况会更糟。所以对每个项目的各种档案进行有效管理很有必要&#xff0c;从公司层…

review what i studied `date` - 2017-4-12

python 连接字符串 int srt >>> a 1 >>> b xuhui >>> a b Traceback (most recent call last):File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for : int and str >>> b str(a)…

StaticFactoryMethod_Level1

以下代码是“简单工厂模式”的第一个例子&#xff1a;

逃离深圳,一个程序员的选择

到新公司上班也已经一个多月了&#xff0c;上周刚刚交了首付&#xff0c;总价50多万&#xff0c;97个平方的房子还外送8个平方。为了不忘记这次的选择&#xff0c;也为了记录这次选择的过程&#xff0c;特撰文如下。 自从得知老婆怀孕后&#xff0c;那是相当高兴&#xff0c;但…

弹出窗口(对话框)

对话框分为三种&#xff1a;window.open方法 无模式对话框 有模式对话框 第一&#xff1a;OPEN方法 <script>functionopen_cate(){ window.open("OpenUp.aspx","","toolbar0,location0,directories0,status0, menubar0,scr…

隐藏系统保留区

为了下午的装机活动自己就安了一个老毛桃&#xff0c;制作启动盘完毕后发现电脑中的“系统保留”盘出来了&#xff0c;为什么会出现这个盘呢&#xff1f; 当笔记本安装Windows7系统时会自己主动产生一个几百兆的系统保留分区。里面保存着系统/磁盘引导的数据。有些笔记本在出厂…

StaticFactoryMethod_Level2

以下代码是“简单工厂模式”的第二个例子&#xff1a;

06- web兼容性测试

稍后更新。。转载于:https://www.cnblogs.com/Chamberlain/p/11064664.html