Scala 部分函数应用语义 + 同步锁定
Scala Partial Function Application Semantics + locking with synchronized
基于基于值相等而不是锁相等的锁,我想出了以下实现:
/**
* An util that provides synchronization using value equality rather than referential equality
* It is guaranteed that if two objects are value-equal, their corresponding blocks are invoked mutually exclusively.
* But the converse may not be true i.e. if two objects are not value-equal, they may be invoked exclusively too
* Note: Typically, no need to create instances of this class. The default instance in the companion object can be safely reused
*
* @param size There is a 1/size probability that two invocations that could be invoked concurrently is not invoked concurrently
*
* Example usage:
* import EquivalenceLock.{defaultInstance => lock}
* def run(person: Person) = lock(person) { .... }
*/
class EquivalenceLock(val size: Int) {
private[this] val locks = IndexedSeq.fill(size)(new Object())
def apply[U](lock: Any)(f: => U) = locks(lock.hashCode().abs % size).synchronized(f)
}
object EquivalenceLock {
implicit val defaultInstance = new EquivalenceLock(1 << 10)
}
我写了一些测试来验证我的锁是否按预期运行:
import EquivalenceLock.{defaultInstance => lock}
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.collection.mutable
val journal = mutable.ArrayBuffer.empty[String]
def log(msg: String) = journal.synchronized {
println(msg)
journal += msg
}
def test(id: String, napTime: Int) = Future {
lock(id) {
log(s"Entering $id=$napTime")
Thread.sleep(napTime * 1000L)
log(s"Exiting $id=$napTime")
}
}
test("foo", 5)
test("foo", 2)
Thread.sleep(20 * 1000L)
val validAnswers = Set(
Seq("Entering foo=5", "Exiting foo=5", "Entering foo=2", "Exiting foo=2"),
Seq("Entering foo=2", "Exiting foo=2", "Entering foo=5", "Exiting foo=5")
)
println(s"Final state = $journal")
assert(validAnswers(journal))
以上测试按预期工作(经过数百万次运行测试)。但是,当我更改以下行时:
def apply[U](lock: Any)(f: => U) = locks(lock.hashCode().abs % size).synchronized(f)
对此:
def apply[U](lock: Any) = locks(lock.hashCode().abs % size).synchronized _
测试失败。
预计:
Entering foo=5
Exiting foo=5
Entering foo=2
Exiting foo=2
或
Entering foo=2
Exiting foo=2
Entering foo=5
Exiting foo=5
实际:
Entering foo=5
Entering foo=2
Exiting foo=2
Exiting foo=5
上面的两段代码应该是相同的,但是对于第二种风格(部分应用的那个)的测试(即 lock(id)
总是同时进入相同的 id
)的代码。为什么?
默认情况下,函数参数是急切求值的。所以
def apply[U](lock: Any) = locks(lock.hashCode().abs % size).synchronized _
相当于
def apply[U](lock: Any)(f: U) = locks(lock.hashCode().abs % size).synchronized(f)
在这种情况下,f 在同步块之前计算。
基于
/**
* An util that provides synchronization using value equality rather than referential equality
* It is guaranteed that if two objects are value-equal, their corresponding blocks are invoked mutually exclusively.
* But the converse may not be true i.e. if two objects are not value-equal, they may be invoked exclusively too
* Note: Typically, no need to create instances of this class. The default instance in the companion object can be safely reused
*
* @param size There is a 1/size probability that two invocations that could be invoked concurrently is not invoked concurrently
*
* Example usage:
* import EquivalenceLock.{defaultInstance => lock}
* def run(person: Person) = lock(person) { .... }
*/
class EquivalenceLock(val size: Int) {
private[this] val locks = IndexedSeq.fill(size)(new Object())
def apply[U](lock: Any)(f: => U) = locks(lock.hashCode().abs % size).synchronized(f)
}
object EquivalenceLock {
implicit val defaultInstance = new EquivalenceLock(1 << 10)
}
我写了一些测试来验证我的锁是否按预期运行:
import EquivalenceLock.{defaultInstance => lock}
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.collection.mutable
val journal = mutable.ArrayBuffer.empty[String]
def log(msg: String) = journal.synchronized {
println(msg)
journal += msg
}
def test(id: String, napTime: Int) = Future {
lock(id) {
log(s"Entering $id=$napTime")
Thread.sleep(napTime * 1000L)
log(s"Exiting $id=$napTime")
}
}
test("foo", 5)
test("foo", 2)
Thread.sleep(20 * 1000L)
val validAnswers = Set(
Seq("Entering foo=5", "Exiting foo=5", "Entering foo=2", "Exiting foo=2"),
Seq("Entering foo=2", "Exiting foo=2", "Entering foo=5", "Exiting foo=5")
)
println(s"Final state = $journal")
assert(validAnswers(journal))
以上测试按预期工作(经过数百万次运行测试)。但是,当我更改以下行时:
def apply[U](lock: Any)(f: => U) = locks(lock.hashCode().abs % size).synchronized(f)
对此:
def apply[U](lock: Any) = locks(lock.hashCode().abs % size).synchronized _
测试失败。
预计:
Entering foo=5
Exiting foo=5
Entering foo=2
Exiting foo=2
或
Entering foo=2
Exiting foo=2
Entering foo=5
Exiting foo=5
实际:
Entering foo=5
Entering foo=2
Exiting foo=2
Exiting foo=5
上面的两段代码应该是相同的,但是对于第二种风格(部分应用的那个)的测试(即 lock(id)
总是同时进入相同的 id
)的代码。为什么?
默认情况下,函数参数是急切求值的。所以
def apply[U](lock: Any) = locks(lock.hashCode().abs % size).synchronized _
相当于
def apply[U](lock: Any)(f: U) = locks(lock.hashCode().abs % size).synchronized(f)
在这种情况下,f 在同步块之前计算。