软件事务内存(Software Transactional Memory,简称 STM)。传统的数据库事务,支持 4 个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),也就是大家常说的 ACID,STM 由于不涉及到持久化,所以只支持 ACI。
STM模拟数据库事务的并发机制来控制并行计算时对共享内存的访问控制。STM更安全和易用,同时又保证了相对较好的可扩展性。是一种代替锁的机制。
在JVM的并发原语中 原子类可以确保多个线程对同一原子类实例安全的并发访问。虽然每个原子性操作本身都是原子性的,但多个原子性操作在整体上不是原子性的。
原子变量组合是一种选择,但在实践中,高效地实现这样的线性化操作还是颇具挑战的。于是出现一个两难选择,用原子性变量组合实现更大的程序会造成竞态,而用synchronized语句则会产生死锁。
幸运的是,STM技术集两者的优点于一身,它支持将简单原子性操作组合成复杂原子性操作,而且不会有产生死锁的风险。
很多STM是以软件库的形式实现的,比如ScalaSTM,它是STM的一种具体实现。
ScalaSTM事务异常处理
当一个异常发生时,ScalaSTM默认 事务对事务变量的修改会被回滚。嵌套事务也会全部回滚。但这个行为是可以修改的。
ScalaSTM使用了一种混合方案。大部分异常会回滚事务,Scala的控制异常则不会回滚。
1.使用 breakable代码块和break
首先创建一个如下图所示的嵌套调用atomic 的结构看异常影响
class TSortedList {
val head = Ref[Node1](null)
override def toString: String = atomic { implicit txn =>
val h = head()
nodeToString(h)
}
import scala.annotation.tailrec
def insert(x: Int): this.type = atomic { implicit txn =>
@tailrec def insert(n: Node1): Unit = {
if (n.next() == null || n.next().elem > x)
n.append(new Node1(x, Ref(null)))
else insert(n.next())
}
if (head() == null || head().elem > x)
head() = new Node1(x, Ref(head()))
else insert(head())
this
}
}
object test01 extends App {
import scala.util.control.Breaks._
val lst = new TSortedList
lst.insert(4).insert(9).insert(1).insert(16)
Future {
atomic { implicit txn =>
println("start!")
breakable {
for (n <- List(1, 4, 9)) {
pop(lst, n)
break
}
println("end!")
}
}
log(s"after removing - $lst")
}
Thread.sleep(1000)
}
程序遇到异常后没有回滚,而在异常发生处直接提交了事务,多层嵌套没有回滚
2.使用 breakable代码块和break
object test02 extends App {
/**
* @注意,事务内抛出的异常可以用catch语句来拦截。这时,嵌套事务的执行就被中止了,从而执行过程从异常捕捉点处继续。
* @在下面的示例中,第二次pop调用产生的异常被捕捉到了。
*/
val lst2 = new TSortedList
lst2.insert(4).insert(9).insert(1).insert(16)
atomic { implicit txn =>
println("start!!")
pop(lst2, 2)
log(s"lst = $lst2")
try { pop(lst2, 3) }
catch { case e: Exception => log(s"Houston... $e!") }
pop(lst2, 1)
}
log(s"result - $lst2")
Thread.sleep(1000)
}
程序执行的图如下:
程序执行被回滚了一次 ,第二次并没有在异常处提交事务,而是异常被捕获了,继续执行atomic 内的代码,直至最终完成事务代码块提交。