给出以下代码
case class Score(value: BigInt, random: Long = randomLong) extends Comparable[Score] {
override def compareTo(that: Score): Int = {
if (this.value < that.value) -1
else if (this.value > that.value) 1
else if (this.random < that.random) -1
else if (this.random > that.random) 1
else 0
}
override def equals(obj: _root_.scala.Any): Boolean = {
val that = obj.asInstanceOf[Score]
this.value == that.value && this.random == that.random
}
}
@tailrec
private def update(mode: UpdateMode, member: String, newScore: Score, spinCount: Int, spinStart: Long): Unit = {
// Caution: there is some subtle logic below, so don't modify it unless you grok it
try {
Metrics.checkSpinCount(member, spinCount)
} catch {
case cause: ConcurrentModificationException =>
throw new ConcurrentModificationException(Leaderboard.maximumSpinCountExceeded.format("update", member), cause)
}
// Set the spin-lock
put(member, None) match {
case None =>
// BEGIN CRITICAL SECTION
// Member's first time on the board
if (scoreToMember.put(newScore, member) != null) {
val message = s"$member: added new member in memberToScore, but found old member in scoreToMember"
logger.error(message)
throw new ConcurrentModificationException(message)
}
memberToScore.put(member, Some(newScore)) // remove the spin-lock
// END CRITICAL SECTION
case Some(option) => option match {
case None => // Update in progress, so spin until complete
//logger.debug(s"update: $member locked, spinCount = $spinCount")
for (i <- -1 to spinCount * 2) {Thread.`yield`()} // dampen contention
update(mode, member, newScore, spinCount + 1, spinStart)
case Some(oldScore) =>
// BEGIN CRITICAL SECTION
// Member already on the leaderboard
if (scoreToMember.remove(oldScore) == null) {
val message = s"$member: oldScore not found in scoreToMember, concurrency defect"
logger.error(message)
throw new ConcurrentModificationException(message)
} else {
val score =
mode match {
case Replace =>
//logger.debug(s"$member: newScore = $newScore")
newScore
case Increment =>
//logger.debug(s"$member: newScore = $newScore, oldScore = $oldScore")
Score(newScore.value + oldScore.value)
}
//logger.debug(s"$member: updated score = $score")
scoreToMember.put(score, member)
memberToScore.put(member, Some(score)) // remove the spin-lock
//logger.debug(s"update: $member unlocked")
}
// END CRITICAL SECTION
// Do this outside the critical section to reduce time under lock
if (spinCount > 0) Metrics.checkSpinTime(System.nanoTime() - spinStart)
}
}
}
有两个重要的数据结构:memberToScore和scoreToMember。我已尝试对memberToScore使用TrieMap[String,Option[Score]]
和ConcurrentHashMap[String,Option[Score]]
,两者都具有相同的行为。
到目前为止,我的测试表明代码是正确的并且线程安全,但神秘之处在于自旋锁的性能。在具有12个硬件线程的系统上,以及12个Futures上的1000次迭代:一直击中相同的成员导致旋转周期为50或更多,但是随机分布的成员可以导致旋转周期为100或更多。如果我不在没有迭代yield()调用的情况下抑制旋转,行为会变得更糟。
所以,这似乎是反直觉的,我期待键的随机分布导致比同一键更少的旋转,但测试证明不是。
任何人都可以对这种反直觉行为提供一些见解吗?
当然,对我的设计可能有更好的解决方案,我对他们持开放态度,但现在我似乎找不到令人满意的解释,我的测试显示了什么,而我的好奇心让我感到饥饿。
另外,虽然单一成员测试的旋转计数上限较低,但随机成员测试的时间旋转上限较低,这正是我所期望的。我无法解释为什么随机成员测试通常会产生更高的旋转计数上限。