同步部分就像访问您的岳父母。 您希望尽可能少出现。 关于锁定,规则是相同的–您想花费最短的时间在关键区域内获取锁定,以防止形成瓶颈。
锁定的核心语言惯用法一直是用于方法和离散块的synced关键字。 这个关键字实际上已硬连接到HotSpot JVM中。 我们在代码中分配的每个对象,无论是String,Array还是成熟的JSON文档,都在本机GC级别的标头中内置锁定功能。 对于JIT编译器(根据特定锁的特定状态和争用级别来编译和重新编译字节码)也是如此。
同步块的问题在于它们全有还是全无 -关键部分中不能有多个线程。 在消费者/生产者方案中,这尤其令人a舌,其中一些线程试图专门编辑一些数据,而另一些线程只是试图读取数据并且可以共享访问权限。
ReadWriteLocks旨在成为此的完美解决方案。 您可以指定哪些线程阻止其他所有人(作家),以及哪些线程与其他人一起使用以消费内容(读者)。 一个快乐的结局? 不怕。
与同步块不同,RW锁不是JVM内置的,并且具有与凡人代码相同的功能。 尽管如此,要实现锁定习惯,您仍然需要指示CPU自动执行特定操作或以特定顺序执行操作,以避免出现竞争情况。 传统上,这是通过进入JVM的神奇门户漏洞- 不安全的类来完成的。 RW锁使用比较和交换(CAS)操作将值直接设置到内存中,作为其线程排队算法的一部分。
即便如此,RWLocks仍然不够快,有时甚至被证明确实很慢 ,以至于不值得为此烦恼。 但是,正在寻求帮助,JDK的好伙伴们没有放弃,现在又有了新的StampedLock 。 该RW锁采用了Java 8 JDK中新增的一组算法和内存隔离功能,以帮助使此锁更快,更可靠。
它兑现了诺言吗? 让我们来看看。
使用锁。 从表面上看,StampedLocks使用起来更复杂。 他们采用的邮票概念是很长的值,可以用作任何锁定/解锁操作使用的票证。 这意味着要解锁R / W操作,您需要为其传递相关的锁定标记。 通过错误的印章,您将面临例外甚至更糟的意外风险。
另一个需要牢记的关键是,与RWLocks不同,StampedLocks 不可重入 。 因此,尽管它们可能更快,但它们有一个缺点,那就是线程现在可以使自己陷入僵局。 实际上,这意味着比以往任何时候都更应该确保锁和图章不会逸出其封闭的代码块。
long stamp = lock.writeLock(); //blocking lock, returns a stamptry {write(stamp); // this is a bad move, you’re letting the stamp escape
}finally {lock.unlock(stamp);// release the lock in the same block - way better
}
我对这种设计的另一个讨厌之处是,邮票被用作长期价值,对您实际上没有任何意义。 我希望使用锁定操作来返回一个描述印记的对象-其类型(R / W),锁定时间,所有者线程等。这将使调试和记录更加容易。 但是,这可能是有意的,它旨在防止开发人员在代码的不同部分之间传递标记,并且还节省了分配对象的成本。
乐观锁 。 就此锁的新功能而言,最重要的部分是新的乐观锁模式。 研究和实践经验表明,读操作在大多数情况下都无法与写操作抗衡。 结果,获得一个成熟的读锁可能被证明是过大的。 更好的方法可能是继续执行读取,并在读取的最后查看该值是否实际上已被修改。 如果是这种情况,您可以重试读取,或升级到较重的锁。
long stamp = lock.tryOptimisticRead(); // non blockingread();if(!lock.validate(stamp)){ // if a write occurred, try again with a read locklong stamp = lock.readLock();try {read();}finally {lock.unlock(stamp);}
}
选锁的最大麻烦之一是,它在生产中的实际行为将根据应用程序状态而有所不同。 这意味着锁定习语的选择不能凭空完成,而必须考虑代码将在其下执行的实际条件。
并发读取器线程与写入器线程的数量将更改您应使用的锁–同步段或RW锁。 由于这些数字在JVM的生命周期中会发生变化,因此这变得更加困难,具体取决于应用程序状态和线程争用。
为了说明这一点,我对不同锁定级别和R / W线程组合下的四种锁定模式进行了压力测试-同步,RW锁定,Stamped RW锁定和RW乐观锁定。 读取器线程将消耗计数器的值,而写入器线程将其从0递增到1M。
5个读取器与5个写入器:堆叠5个并发读取器和5个写入器线程,我们看到加盖的锁发光了,性能比同步提高了3倍。 RW锁定也表现良好。 奇怪的是,乐观锁在表面上应该是最快的,但实际上却是最慢的 。
1 0个读者与10个作家:接下来,我将争用级别提高到10个作家和10个读者线程。 在这里,事情开始发生重大变化。 RW锁现在比在相同级别上执行的加盖和同步锁慢一个数量级。 请注意,令人惊讶的是乐观锁仍然是慢速加盖的RW锁。
16位读者与4位作家:接下来,我保持较高的竞争水平,同时又使平衡趋于有利于读者线程:16位读者与4位作家。 RW锁继续说明了其被替换的原因- 速度慢了一百倍 。 Stamped和Optimistic的性能很好,同步性也不差。
19位读者与1位读者:最后,我研究了单个作者线程对19位读者的影响。 请注意,结果要慢得多,因为单线程需要更长的时间才能完成工作。 在这里,我们得到一些非常有趣的结果。 毫不奇怪,RW锁需要无穷大才能完成。 但是,冲压锁定的性能并没有好得多……乐观锁定显然是赢家,比RW锁定高100倍。即使如此,请记住,这种锁定方式可能会使您失败,因为在此期间可能会发生写入器。 我们忠实的老客户,同步化继续取得可喜的成果。
完整的结果可以在这里找到……硬件:MBP四核i7。
基准代码可以在这里找到。
结论
似乎平均而言,内在同步锁仍可提供总体上最佳的性能。 即使这样,这里的意思并不是说它将在所有情况下都表现最佳。 主要是为了表明, 在将代码投入生产之前 ,应该基于测试预期的争用级别以及读取器和写入器线程之间的划分来选择锁定习惯用法。 否则,您将冒着严重的生产调试痛苦的风险。
在此处了解有关StampedLocks的更多信息 。
对基准有疑问,意见或建议吗? 让我知道!
翻译自: https://www.javacodegeeks.com/2014/06/java-8-stampedlocks-vs-readwritelocks-and-synchronized.html