0

ロックの等価性ではなく値の等価性に基づくロックに関する以前の質問に基づいて、次の実装を思いつきました。

/**
  * 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

上記の 2 つのコードは同じである必要がありますが、コードの 2 番目のフレーバー (部分的に適用されたもの)のテスト (つまり、lock(id)同じ に対して常に同時に入力されます) です。idなんで?

4

1 に答える 1

2

デフォルトでは、関数パラメータは熱心に評価されます。そう

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 は同期ブロックの前に評価されます。

于 2016-09-07T18:11:10.320 に答える