软件事务内存导论(七)阻塞事务
我们经常会遇到这样一种情况,即某事务T能否成功完成依赖于某个变量是否发生了变化,并且由于这种原因所引起的事务运行失败也可能只是暂时性的。作为对这种暂时性失败的响应,我们可能会返回一个错误码并告诉事务T等待一段时间之后再重试。然而在事务T等待期间,即使其他任务已经更改了事务T所依赖的数据,事务T也没法立即感知到并重试了。为了解决这一问题,Akka为我们提供了一个简单的工具——retry(),该函数可以先将事务进行回滚,并将事务置为阻塞状态直到该事物所依赖的引用对象发生变化或事务阻塞的时间超过了之前配置的阻塞超时为止。我本人更愿意将这一过程称为“有意识地等待”,因为这种说法听起来比“阻塞”更合适一些。下面让我们将阻塞(或有意识地等待)用于下面的两个例子当中。
在Java中阻塞事务
程序员一般都会对咖啡因上瘾,所以加班的时候任何主动要去拿些咖啡回来喝的人都知道不能空手而归。但是这个拿咖啡的人很聪明,他没有忙等(busy wait)至咖啡罐被重新填满,而是在Akka的帮助下给自己设置了一个消息提醒,一旦咖啡罐有变化他就能收到这个通知。下面让我们用retry()来实现这个可以有意识等待的fillCup()函数。
在fillCup()函数中,我们将事务配置成blockingAllowed,并将事务完成的超时时间设为6秒。当发现当前没有足够数量的咖啡时,fillCups()函数没有简单地返回一个错误码,而是调用了StmUtil的retry()函数进行有意识地等待。这将使得当前事务进入阻塞状态,直到与之相关的cups引用发生变化为止。一旦有任何相关的引用发生改变,系统将启动一个新事务将之前包含retry的原子性代码进行重做。
下面让我们通过调用fillCup()函数来观察retry()的实际效果:
在main()函数中,我们启动了一个每隔大约5秒就往咖啡壶重新装填咖啡的定时任务。随后,第一个跑去拿咖啡的同事A立即取走了20杯咖啡。紧接着,当我们这边自告奋勇去取咖啡的同事B想再取走10杯咖啡时,他的动作将被阻塞直至重新装填任务完成为止,而这种等待要比不断重试的方案高效得多。重新装填的事务完成之后,同事B的请求将被自动重试,而这一次他的请求成功完成了。如果重新装填任务没有在我们设定的超时时间内发生,则请求咖啡的事务将会失败,在上例的try代码块中的那个请求就属于这种情况。我们可以通过输出日志来观察到这一行为,同时也可以更深入地体会到retry()为我们带来的便利:
filled up....20 ........ at 0.423589 retry........ at 0.425385 retry........ at 0.427569 Refilling.... at 5.130381 filled up....10 ........ at 5.131149 retry........ at 5.131357 retry........ at 5.131521 Failed: Transaction DefaultTransaction has timed with a total timeout of 6000000000 ns
从上述输出结果中我们可以看到,第一个倒20杯咖啡的请求是在程序开始运行之后0.4秒左右完成的。而第一个请求完成之后,咖啡壶里就仅剩余4杯咖啡了,所以第二个倒10杯咖啡的请求就只能被阻塞,直到程序运行至5秒左右时重新装填的任务完成为止。在重新装填的任务完成之后,倒10杯咖啡的事务被重新启动,并在程序运行到5秒多一点的时候成功完成。最后一个倒22杯咖啡的任务则由于在规定的超时时间内没有再次发生重新装填而以失败告终。
其实我们在日常工作中并不会经常用到retry(),只有当程序逻辑需要执行某些操作、而这些操作又依赖于某些相关数据发生变化的情况下,我们才能受益于这个监控数据变化的特性。
在Scala中阻塞事务
在上面Java版的示例中,我们使用了一个提供了很多STM相关的便利接口的StmUtils对象。而在Scala中,我们可以直接使用StmUtils里提供的各种特性(trait)。此外,我们在Scala中同样可以用工厂方法来创建TransactionFactory。
在创建TransactionFactory对象时,我们并没有直接使用DurationInt来配置事务的超时时间,而是用intToDurationInt()函数来完成从int到DurationInt的隐式转换。通过Scala隐式转换所带来的语法上的便利,我们在初始化TransactionFactory对象时只需简单调用6 seconds即可。示例代码中余下的部分就只是从Java到Scala的一个简单的翻译而已,其最终的结果输出如下所示:
filled up....20 ........ at 0.325964 retry........ at 0.327425 retry........ at 0.329587 Refilling.... at 5.105191 filled up....10 ........ at 5.106074 retry........ at 5.106296 retry........ at 5.106466 Failed: Transaction DefaultTransaction has timed with a total timeout of 6000000000 ns
CleanMyMac X稳定版苹果电脑磁盘内存清理软件 Mac电脑用的时间久了,Mac用户尤其是MacBook用户会经常收到“磁盘几乎已满”的提示,如何解决这个问题,当我们使用苹果MAC一段时间后,就会有大量的垃圾文件占用磁盘空间,例如系统缓存文件、应用程序缓存文件、备份和重复文件、旧版的应用程序及其部件等,为了不影响电脑的后续使用,我们需要经常清理磁盘空间,这里分享一个处理磁盘空间不足提示的小方法。CleanMyMac X下载:http://t.csdn.cn/ye9sT
Android 7.1 内置“一键清理内存”功能,专治流氓勒索软件 本文讲的是Android 7.1 内置“一键清理内存”功能,专治流氓勒索软件,在打击Android恶意勒索软件上,Google最近迈出了一大步,它推出的一项隐藏新功能,让用户在中招后还能保持对设备的控制权。
软件事务内存导论(七)阻塞事务 阻塞事务——有意识地等待 我们经常会遇到这样一种情况,即某事务T能否成功完成依赖于某个变量是否发生了变化,并且由于这种原因所引起的事务运行失败也可能只是暂时性的。作 为对这种暂时性失败的响应,我们可能会返回一个错误码并告诉事务T等待一段时间之后再重试。
ali清英 方腾飞,花名清英,英文名kiral,并发编程网创始人,支付宝技术专家,《Java并发编程的艺术》作者。
相关文章
- JVM内存结构图解
- 浅谈js的内存与闭包
- Java虚拟机(JVM)-- Dump内存快照
- 【深度长文】学好并发编程不一定需要了解的MESI和内存屏障
- 驱动开发:内核MDL读写进程内存
- 【Android 逆向】Android 逆向基本概念 ( 软件运行时内存结构 | 文件与内存之间的联系 )
- 【Linux 内核 内存管理】引导内存分配器 bootmem ① ( 引导内存分配器 bootmem 工作机制 | 引导内存分配器 bootmem 的描述 bootmem_data 结构体 )
- OPPO内存拓展技术曝光:12GB秒变19GB 老用户有福了
- Linux 为何占用内存大?(linux占用内存大)
- 内存使用查看Redis内存使用:管理你的缓存(redis查看)
- 优化Oracle数据库减少内存消耗(oracle 内存消耗)
- 掌握Oracle内存使用情况介绍查看命令(oracle内存查看命令)
- Oracle实现内存全面优化(oracle内存全面分析)
- 构建基于内存的Redis数据库(内存实现redis)
- Redis订阅功能导致内存溢出(redis订阅使内存溢出)
- Oracle12c引入新一代内存数据库架构(oracle12c内存库)