java stampedlockk可以替代lock吗

Java 8:StampedLock、ReadWriteLock以及synchronized的比较 - 为程序员服务
Java 8:StampedLock、ReadWriteLock以及synchronized的比较
译者:iDestiny
校对:郭蕾
同步区有点像拜访你的公公婆婆。你当然是希望待的时间越短越好。说到锁的话情况也是一样的,你希望获取锁以及进入临界区域的时间越短越好,这样才不会造成瓶颈。
对于方法和代码块,语言层面的加锁机制是synchronized关键字,该关键字是由HotSpot虚拟机内置的。我们在代码中分配的每一个对象,如String、Array或者一个完整的JSON文档,在本地垃圾回收级别都具有内置的加锁能力。JIT编译器也是类似的,它在进行字节码的编译和反编译的时候,都取决于特定的某个锁的具体的状态和竞争级别。
同步块的关键是:进入临界区域内的线程不能超过一个 。这一点对于生产者消费者场景中来说非常糟糕,当一些线程独占地修改某些数据时,而另外一些线程只是希望读取数据,这个是可以和别的线程同时进行的。
读写锁(ReadWriteLock)是这种情况最好的解决方案。你指定哪些线程可以阻塞其他线程(写线程),哪些线程可以与其他线程共享数据(读线程)。这是一个完美的解决方案?恐怕不是。
读写锁不像同步块,它不是JVM内建的,它只不过是段普通的代码。为了实现加锁的语义,它得命令CPU原子地或者以特定的顺序执行操作,以避免竞态条件。这通常都是通过JVM预留的一个后门来实现的——Unsafe类。读写锁使用比较并交换(CAS)操作直接将值设置到内存中去,这是它们线程排队算法中的一部分。
即便如此,读写锁还是不够快,并且有时候慢得要死,慢到你觉得就不应该使用它。然而JDK的伙计们并没有放弃读写锁,现在他们带来了一个全新的StampedLock。StampedLock使用了一组新的算法以及Java 8 JDK中引入的内存屏障的特性,这使得这个锁更高效也更健壮。
它兑现了自己的诺言了吗?让我们拭目以待。
使用锁。从表面上看StampedLock使用起来更复杂。它们使用了一个票据(stamp)的概念,这是一个long值,在加锁和解锁操作时,它被用作一张门票。这意味着要解锁一个操作你需要传递相应的的门票。如果传递错误的门票,那么可能会抛出一个异常,或者其他意想不到的错误。
另外一个值得关注的重要问题是,不像ReadWriteLock,StampedLocks是不可重入的。因此尽管StampedLocks可能更快,但可能产生死锁。在实践中,这意味着你应该始终确保锁以及对应的门票不要逃逸出所在的代码块。
long stamp = lock.writeLock(); //blocking lock, returns a stamp
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
这个设计还有个让人无法忍受的地方就是这个long类型的票据对你而言没有任何意义。我希望锁操作返回的是一个描述票据的对象——包括它的类型(读/写)、加锁时间、所有者线程等等。这样处理的话更容易调试和跟踪日志。不过这么做很有可能是故意的,以便阻止开发人员不要将这个戳在代码里传来传去,同时也减少了分配对象的开销。
乐观锁。StampedLocks最重要的一个新功能就是它的乐观锁模式。研究和实践经验表明,读操作是在大多数情况下不会与写操作竞争。因此,获取全占的读锁可能就如杀鸡用牛刀了。一个更好的方法可能是继续执行读,并且结束后同时判断该值是否被修改,如果被修改,你再进行重试,或者升级成一个更重的锁。
long stamp = lock.tryOptimisticRead(); // non blocking
if(!lock.validate(stamp)){ // if a write occurred, try again with a read lock
long stamp = lock.readLock();
} finally {
lock.unlock(stamp);
选择一个锁,最大的难点之一是其在生产环境中的表现会因应用状态的不同而有所差异。也就是说你不能凭空选择使用何种锁,而是得将代码执行的具体环境也考虑进来 。
并发读写线程的数量将决定你应该使用哪一种锁——同步块或者读写锁。如果这些线程数在JVM的执行生命周期内发生改变的话,这个问题就更棘手了,这取决于应用的状态以及线程的竞争级别。
为了解释,我对四种模式下的锁分别进行了压力测试——在不同竞争级别和读写线程组合下的synchronized、读写锁、StampedLock的读写锁以及读写乐观锁。读线程将读取一个计数器的值,写线程会将它从0增加到1M。
5个读线程和5个写线程:5个读写线程分别在并发地执行,我们发现StampedLock表现得最好,比synchronized性能高3倍多。读写锁也表现得不错。这里奇怪的是乐观锁,表面上看它该是最快的,实际却是最慢的。
10个读线程和10个写线程:接下来,我增加竞争级别提高到10个读线程和10个写线程。现在情况开始发生了变化。在同级别执行下,读写锁现在要比StampedLock和synchronized慢一个数量级。请注意,乐观锁令人惊讶的仍然比StampedLock的读写锁慢。
16个读线程和4个写线程:接下来,我保持同样的竞争级别,不过将读写线程的比重调整了下:16个读线程和4个写线程。读写锁再说次说明了为什么它要被替换掉了——它慢了百倍以上。Stamped以及乐观锁都表现得不错,synchronized也紧随其后。
19个读线程和1个写线程:最后,我看看19个读线程和1个写线程会怎样。注意到结果慢得多了,因为单线程需要更长的时间来完成计数增加操作。在这里我们得到了一些非常有趣的结果。读写锁需要太多时间来完成。尽管Stamped锁没有表现得很好…乐观锁明显是这里的赢家,打败了读写锁100倍。即使如此,记住这种锁定模式可能会失败,因为这段时间内可能会出现一个写线程。Synchronized, 我们的老朋友,继续表现出可靠的结果。
完整的结果可以在找到。硬件:MBP, Core i7。
基础测试代码可以在下载。
总体看来, 整体性能表现最好的仍然是内置的同步锁。但是,这里并不是说内置的同步锁会在所有的情况下都执行得最好。这里主要想表达的是在你将你的代码投入生产之前,应该基于预期的竞争级别和读写线程之间的分配进行测试,再选择适当一个适当的锁。否则你会面临线上故障的风险。
其他的关于StampedLocks的资料请点击。
(全文完)如果您喜欢此文请点赞,分享,评论。
原创文章转载请注明出处:
小额赞助本站::
您可能感兴趣的文章
让天下没有难学的技术
原文地址:, 感谢原作者分享。
您可能感兴趣的代码Java 8:StampedLock,ReadWriteLock以及synchronized的比较
同步区域有点像拜访你的公公婆婆。你当然是希望待的时间越短越好。说到锁的话情况也是一样的,你希望获取锁以及进入临界区域的时间越短越好,这样才不会造成瓶颈。
synchronized关键字是语言层面的加锁机制,它可以用于方法以及代码块。这个关键字是由HotSpot JVM来实现的。我们在代码中分配的每一个对象,比如String, Array或者一个JSON文档,在GC的层面的对象头部,都内建了一个加锁的机制。JIT编译器也是类似的,它在进行字节码的编译和反编译的时候,都取决于特定的某个锁的具体的状态和竞争级别。
同步块的一个问题在于——进入临界区域内的线程不能超过一个。这对生产者消费者场景是一个很大的打击,尽管这里有些线程会尝试进行独占式的数据编辑,而另外一些线程只是希望读取一下数据,这个是可以和别的线程同时进行的。
读写锁(ReadWriteLock)是一个理想的解决方案。你可以指定哪些线程会阻塞别的操作(写线程),哪些线程可以和别人共同进行内容的消费(读线程)。一个美满的结局?恐怕不是。
读写锁不像同步块,它并不是JVM中内建支持的,它只不过是段普通的代码。同样的,要想实现锁机制,它得引导CPU原子地或者按某个特定的顺序来执行某些特定的操作,以避免竞争条件。这通常都是通过JVM预留的一个后门来实现的——unsafe类。读写锁使用的是CAS操作来将值直接设置到内存中去,这是它们线程排队算法中的一部分。
尽管这样,读写锁也还不够快,有时候甚至会表现得非常慢,慢到你压根儿就不应该使用它。然而JDK的这帮家伙们没有放弃治疗,现在他们带来了一个全新的StampedLock。这个读写锁使用了一组新的算法以及Java 8 JDK中引入的内存屏障的特性,这使得这个锁更高效也更健壮。
它兑现了自己的诺言了吗?让我们拭目以待。
StampedLock的用法 更为复杂。它使用了一个戳(stamp)的概念,这是一个long值,它用作加锁解锁操作的一个标签。这意味着想要解锁一个操作你得将它对应的戳给传递进去。如果你传入的戳是错误的,那么可能会抛出一个异常,或者更糟糕的是,无法预知的行为。
另外一个值得关注的重要问题是,StampedLock并不像ReadWriteLock,它不是可重入的。因此它虽然更快,但也有一个坏处是线程可能会自己产生死锁。在实践中,这意味着你应该始终确保锁以及对应的戳不要逃逸出所在的代码块。
long stamp = lock.writeLock();
//blocking lock, returns a stamp
write(stamp); // this is a bad move, you’re letting the stamp escape
lock.unlock(stamp);// release the lock in the same block - way better
这个设计还有个让人无法忍受的地方是这个long类型的戳对你而言没有任何意义。我还是希望锁操作能返回一个描述这个戳的对象——包括它的类型,锁的时间,owner线程,等等。这让调试和跟踪日志变得更简单。不过这么做很有可能是故意的,以便阻止开发人员不要将这个戳在代码里传来传去,同时也减少了分配对象的开销。
这个锁最重要的一个新功能就是它的乐观锁模式。研究和实践表明,读操作占了绝大数,并且很少和写操作竞争 。因此,使用一个成熟的读锁有点浪费。更好的方式是直接去读,结束之后再看一下这段时间内这个值有没有被改动过。如果有的话,你再进行重试,或者升级成一个更重的锁。
long stamp = lock.tryOptimisticRead(); // non blocking
if(!lock.validate(stamp)){ // if a write occurred, try again with a read lock
long stamp = lock.readLock();
lock.unlock(stamp);
使用锁最大的麻烦在于,它在生产环境的实际表现取决于应用的状态。这意味着你不能凭空选择使用何种锁,而是得将代码执行的具体环境也考虑进来 。
并发的读写线程数会影响到你具体使用哪种锁——同步区还是读写锁。如果这些线程数在JVM的执行生命周期内发生改变的话,这个问题就更棘手了,这取决于应用的状态以及线程的竞争级别。
为了能说明这点,我对四种模式下的锁进行了压测——synchronized,读写锁,StampedLock的读写锁,以及读写乐观锁,分别使用了不同的竞争级别以及不同读写线程数的组合。读线程会去消费一个计数器的值,而写线程会将它从0增加到1M。
5个读线程,5个写线程
5个读写线程分别在并发地执行,可以看到StampedLock明显胜出了,它的性能要比synchronized高出3倍。读写锁的性能也不错。奇怪的是,乐观锁,表面看起来应该是最快的,实际上这里却是最慢的。
10个读线程,10个写线程
下面,我将竞争级别提高到10个写线程和10个读线程。现在情况开始发生变化了。读写锁要比StampedLock以及synchronized慢了一个数量级。说到乐观锁还是很让人意外,它比StampedLock的读写锁还要慢。
16个读线程,4个写线程
下面,我保持同样的竞争级别,不过将读线程的比重调整了下:16个读,4个写。读写锁再说次说明了为什么它要被替换掉了——它慢了百倍以上。Stamped以及乐观锁都表现得不错,synchronized也紧随其后。
19个读,1个写
最后,我只留了一个写线程,剩下19个全是读。注意到结果更慢了,因为单个线程完成任务的时间会更长。这里的结果非常有意思。读定锁看起来像是完成不了了。StampedLock的话也不太理想——乐观锁在这里明显胜出,百倍于读写锁。需要记住的是这个模式下它可能会失败,因为写操作可能会在你读的时候出现。synchronized,我们忠实的老伙伴,依旧保持着很稳定的表现。
完整的结果可以在下载。硬件:Macbook Pro i7
测试代码见。
看起来平均表现最佳的还是内部实现的synchronized锁。尽管如此,并不是说它在所有情况下都是表现得最好的。主要是想告诉你,采用哪种锁,应该在你的代码上线前在不同的竞争级别下,并且使用不同的读写线程数来进行详细的测试,根据结果来选择。否则你会面临线上故障的风险。
原创文章转载请注明出处:
本文已收录于以下专栏:
相关文章推荐
由于ReadWriteLock存在严重的性能问题,甚至在很多情况下比synchronized要慢,Java8新出了StampedLock,这个锁不仅更快,而且它提供强大的乐观锁API,这意味着你能以一...
import java.util.concurrent.locks.StampedLpublic class Test {
private String str = &init&;
Java8就像一个宝藏,一个小的API改进,也足与写一篇文章,比如同步,一直是多线程并发编程的一个老话题,相信没有人喜欢同步的代码,这会降低应用的吞吐量等性能指标,最坏的时候会挂起死机,但是即使这样你...
synchronized与Lock  synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢?  在上面一篇文章中,我们了解到如果一个代码块被s...
在java多线程编程中,可能我们经常会遇到线程同步的问题,可以使用synchronized或者Lock去控制同步锁,他们都能实现线程的同步,下面来分析下这两种方式的区别:1.synchronized可...
StampedLock实现概述StampedLock与之前的ReentrantLock,ReentrantReadWriteLock使用队列同步列AQS实现有所不同,StampedLock的state...
* Non-exclusively acquires the lock, blocking if necessary
* until available.
StampedLock概述StampedLock是从JDK1.8开始引入的,它的出现对于ReentrantReadWriteLock在读多写少的情况下的效率问题还有写线程容易产生“饥饿”的问题有了很大...
首先了解悲观锁和乐观锁概念
悲观锁:假设会发生并发冲突,屏蔽一切可能违反数据完整性的操作
读取悲观锁:在读取之前先判断以下,数据有无修改。
乐观锁:假设不会发生并发冲突,只在提交操作前...
在并发编程中,多线程同时并发访问的资源叫做临界资源,当多个线程同时访问对象并要求操作相同资源时,分割了原子操作就有可能出现数据的不一致或数据不完整的情况,为避免这种情况的发生,我们会采取同步机制,以确...
他的最新文章
讲师:AI100
讲师:谢梁
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)让天下没有难学的技术
作者:Adrianos Dadis 译者:买蓉(sky.) 校对:方腾飞
在高负载多线程应用中性能是非常重要的。为了达到更好的性能,开发者必须意识到并发的重要性。当我们需要使用并发时, 常常有一个资源必须被两个或多个线程共享。
在这种情况下,就存在一个,也就是其中一个线程可以得到锁(锁与特定资源绑定),其他想要得到锁的线程会被阻塞。这个同步机制的实现是有代价的,为了向你提供一个好用的同步模型,JVM和操作系统都要消耗资源。有三个最重要的因素使并发的实现会消耗大量资源,它们是:
上下文切换
(4 votes, average: 4.00 out of 5)
Loading...
感谢[]同学的投稿,投稿可将文章发送到
类锁和对象锁是否会冲突?对象锁和私有锁是否会冲突?通过实例来进行说明。
一、相关约定
为了明确后文的描述,先对本文涉及到的锁的相关定义作如下约定:
1. 类锁:在代码中的方法上加了static和synchronized的锁,或者synchronized(xxx.class)的代码段,如下文中的increament();
2.对象锁:在代码中的方法上加了synchronized的锁,或者synchronized(this)的代码段,如下文中的synOnMethod()和synInMethod();
3.私有锁:在类内部声明一个私有属性如private Object lock,在需要加锁的代码段synchronized(lock),如下文中的synMethodWithObj()。
(5 votes, average: 3.20 out of 5)
Loading...
作者:山鸡
锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质以及类型却很少被提及。本系列文章将分析JAVA下常见的锁名称以及特性,为大家答疑解惑。
(4 votes, average: 4.25 out of 5)
Loading...
作者:山鸡
锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质以及类型却很少被提及。本系列文章将分析JAVA中常见的锁以及其特性,为大家答疑解惑。
(13 votes, average: 3.92 out of 5)
Loading...
作者:Pedro Ramalhete,译者:周可人,校对:梁海舰
Tick lock是mutual lock的一种简单实现:
它是由John Mellor-Crummey和Michael Scott在1991年提出的(pdf中2.2节),感谢C++11和C11中新的内存模型,我们可以对单个变量进行具有屏障或者不具有屏障的原子操作。当屏障没有使用,只有原子性保证时,我们称之为“relaxed atomic”:
注意在C11/C++11内存模型中,一个原子变量并不具有内在的“顺序一致性”或者“relaxed”性质,然而我们可以在每次访问的时选择它的行为。
原子修饰符只能保证原子性,即这个变量会被单步读或写。其他语言,如Java和Scala则不同,它们可以保证几乎所有的原生类型提供原子性保证,从而表现为“relaxed atomic”。并且,所有被声明为顺序一致性的变量可以在整个程序中保持性质(除非在Java中使用sun.misc.unsafe)。尽管这个细微的差异可能看起来并不重要,但是当我们的目标是从同步或是并发算法中挖掘最大性能时,就需要关注这个差异了。
(2 votes, average: 3.00 out of 5)
Loading...
译者:曹姚君 校对: 方腾飞
上周在由通过组织的开放空间会议(unconference)上,我参加一个新的java规范
的审查会议。StampedLock 是为了解决多个readers 并发访问共享状态时,系统出现的内存地址竞争问题。在设计上通过使用乐观的读操作,StampedLock 比更加高效;
在会议期间,我突然意思到两点:
第一、我想是时候该去回顾java中锁的实现的现状。
第二、虽然StampedLock 看上去是JDK很好的补充,但是它视乎忽略了一个事实,即在多个reader的场景里,无锁的算法通常是更好的解决方案。
(4 votes, average: 4.25 out of 5)
Loading...
声明:本文是《 》的第八章, 作者: Javier Fernández González 译者:郑玉婷
校对:方腾飞
监控Lock接口
Lock 接口是Java 并发 API提供的最基本的机制来同步代码块。它允许定义临界区。临界区是代码块可以共享资源,但是不能被多个线程同时执行。此机制是通过Lock 接口和 ReentrantLock 类实现的。
在这个指南,你将学习从Lock对象可以获取的信息和如何获取这些信息。
(1 votes, average: 5.00 out of 5)
Loading...
声明:本文是《 》的第七章,作者: Javier Fernández González
译者:许巧辉
实现一个自定义的Lock类
锁是Java并发API提供的基本同步机制之一。它允许程序员保护代码的临界区,所以,在某个时刻只有一个线程能执行这个代码块。它提供以下两种操作:
lock():当你想要访问一个临界区时,调用这个方法。如果有其他线程正在运行这个临界区,其他线程将阻塞,直到它们被这个锁唤醒,从而获取这个临界区的访问。
unlock():你在临界区的尾部调用这个方法,允许其他线程访问这个临界区。
在Java并发API中,锁是在Lock接口及其一些实现类中声明的,比如ReentrantLock类。
在这个指南中,你将学习如何实现你自己的Lock对象,它将实现一个实现了Lock接口并可用来保护临界区的类。
(2 votes, average: 4.50 out of 5)
Loading...
声明:本文是《 》的第二章,作者: Javier Fernández González
译者:许巧辉 校对:方腾飞
在Lock中使用多个条件
一个锁可能伴随着多个条件。这些条件声明在Condition接口中。 这些条件的目的是允许线程拥有锁的控制并且检查条件是否为true,如果是false,那么线程将被阻塞,直到其他线程唤醒它们。Condition接口提供一种机制,阻塞一个线程和唤醒一个被阻塞的线程。
在并发编程中,生产者与消费者是经典的问题。我们有一个数据缓冲区,一个或多个数据生产者往缓冲区存储数据,一个或多个数据消费者从缓冲区中取出数据,正如在这一章中前面所解释的一样。
在这个指南中,你将学习如何通过使用锁和条件来实现生产者与消费者问题。
(2 votes, average: 5.00 out of 5)
Loading...
声明:本文是《 》的第二章,作者: Javier Fernández González
译者:许巧辉 校对:方腾飞
修改Lock的公平性
在ReentrantLock类和 ReentrantReadWriteLock类的构造器中,允许一个名为fair的boolean类型参数,它允许你来控制这些类的行为。默认值为 false,这将启用非公平模式。在这个模式中,当有多个线程正在等待一把锁(ReentrantLock或者 ReentrantReadWriteLock),这个锁必须选择它们中间的一个来获得进入临界区,选择任意一个是没有任何标准的。true值将开启公平 模式。在这个模式中,当有多个线程正在等待一把锁(ReentrantLock或者ReentrantReadWriteLock),这个锁必须选择它们 中间的一个来获得进入临界区,它将选择等待时间最长的线程。考虑到之前解释的行为只是使用lock()和unlock()方法。由于tryLock()方 法并不会使线程进入睡眠,即使Lock接口正在被使用,这个公平属性并不会影响它的功能。
在这个指南中,我们将修改使用Lock同步代码块食谱示例来使用这个属性,并且观察公平与非公平模式之间的差别。
(2 votes, average: 5.00 out of 5)
Loading...
声明:本文是《 》的第二章,作者: Javier Fernández González
译者:许巧辉
使用读/写锁同步数据访问
锁所提供的最重要的改进之一就是ReadWriteLock接口和唯一 一个实现它的ReentrantReadWriteLock类。这个类提供两把锁,一把用于读操作和一把用于写操作。同时可以有多个线程执行读操作,但只有一个线程可以执行写操作。当一个线程正在执行一个写操作,不可能有任何线程执行读操作。
在这个指南中,你将会学习如何使用ReadWriteLock接口实现一个程序,使用它来控制访问一个存储两个产品价格的对象。
(4 votes, average: 3.75 out of 5)
Loading...
声明:本文是《 》的第二章,作者: Javier Fernández González
译者:许巧辉 校对:方腾飞
使用Lock同步代码块
Java提供另外的机制用来同步代码块。它比synchronized关键字更加强大、灵活。它是基于Lock接口和实现它的类(如ReentrantLock)。这种机制有如下优势:
它允许以一种更灵活的方式来构建synchronized块。使用synchronized关键字,你必须以结构化方式得到释放synchronized代码块的控制权。Lock接口允许你获得更复杂的结构来实现你的临界区。
Lock 接口比synchronized关键字提供更多额外的功能。新功能之一是实现的tryLock()方法。这种方法试图获取锁的控制权并且如果它不能获取该锁,是因为其他线程在使用这个锁,它将返回这个锁。使用synchronized关键字,当线程A试图执行synchronized代码块,如果线程B正在执行它,那么线程A将阻塞直到线程B执行完synchronized代码块。使用锁,你可以执行tryLock()方法,这个方法返回一个 Boolean值表示,是否有其他线程正在运行这个锁所保护的代码。
当有多个读者和一个写者时,Lock接口允许读写操作分离。
Lock接口比synchronized关键字提供更好的性能。
在这个指南中,你将学习如何通过锁来同步代码块和通过Lock接口及其实现者ReentrantLock类来创建临界区,实现一个程序来模拟打印队列。
(6 votes, average: 4.33 out of 5)
Loading...
作者:paul
译者:谢宝友,鲁阳,陈渝
在过去几十年并发研究领域的出版物中,锁总是扮演着坏人的角色,锁背负的指控包括引起死锁、锁封护(luyang注:lock convoying,多个同优先级的线程重复竞争同一把锁,此时大量虽然被唤醒而得不到锁的线程被迫进行调度切换,这种频繁的调度切换相当影响系统性能)、饥饿、不公平、data races以及其他许多并发带来的罪孽。有趣的是,在共享内存并行软件中真正承担重担的是——你猜对了——锁。
图1.1:锁:坏人还是懒汉?
(2 votes, average: 1.00 out of 5)
Loading...
译者:张坤等
(未翻译)
(56 votes, average: 4.54 out of 5)
Loading...
校对:方腾飞
如果一个线程因为CPU时间全部被其他线程抢走而得不到CPU运行时间,这种状态被称之为“饥饿”。而该线程被“饥饿致死”正是因为它得不到CPU运行时间的机会。解决饥饿的方案被称之为“公平性” – 即所有线程均能公平地获得运行机会。
下面是本文讨论的主题:
1. Java中导致饥饿的原因:
高优先级线程吞噬所有的低优先级线程的CPU时间。
线程被永久堵塞在一个等待进入同步块的状态。
线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait方法)。
2. 在Java中实现公平性方案,需要:
使用锁,而不是同步块。
注意性能方面。
(11 votes, average: 4.73 out of 5)
Loading...Java8新特性之StampedLock_教育指南_百度教育攻略
Java8就像一个宝藏,一个小的API改进,也足与写一篇文章,比如同步,一直是多线程并发编程的一个老话题,相信没有人喜欢同步的代码,这会降低应用的吞吐量等性能指标,最坏的时候会挂起死机,但是即使这样你也没得选择,因为要保证信息的正确性。所以本文决定将从synchronized、Lock到Java8新增的StampedLock进行对比分析,相信StampedLock不会让大家失望。synchronized在java5之前,实现同步主要是使用synchronized。它是Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。精彩内容,尽在百度攻略:有四种不同的同步块:1. 实例方法2. 静态方法精彩内容,尽在百度攻略:3. 实例方法中的同步块4. 静态方法中的同步块大家对此应该不陌生,所以不多讲了,以下是代码示例精彩内容,尽在百度攻略:synchronized(this)// do operation}精彩内容,尽在百度攻略:小结:在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,性能上也有所提升。Lock它是Java 5在java.util.concurrent.locks新增的一个API。精彩内容,尽在百度攻略:Lock是一个接口,核心方法是lock(),unlock(),tryLock(),实现类有ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock;ReentrantReadWriteLock, ReentrantLock 和synchronized锁都有相同的内存语义。与synchronized不同的是,Lock完全用Java写成,在java这个层面是无关JVM实现的。Lock提供更灵活的锁机制,很多synchronized 没有提供的许多特性,比如锁投票,定时锁等候和中断锁等候,但因为lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中精彩内容,尽在百度攻略:下面是Lock的一个代码示例rwlock.writeLock().lock();try {精彩内容,尽在百度攻略:// do operation} finally {rwlock.writeLock().unlock();精彩内容,尽在百度攻略:}小结:比synchronized更灵活、更具可伸缩性的锁定机制,但不管怎么说还是synchronized代码要更容易书写些StampedLock精彩内容,尽在百度攻略:它是java8在java.util.concurrent.locks新增的一个API。ReentrantReadWriteLock 在沒有任何读写锁时,才可以取得写入锁,这可用于实现了悲观读取(Pessimistic Reading),即如果执行中进行读取时,经常可能有另一执行要写入的需求,为了保持同步,ReentrantReadWriteLock 的读取锁定就可派上用场。然而,如果读取执行情况很多,写入很少的情况下,使用 ReentrantReadWriteLock 可能会使写入线程遭遇饥饿(Starvation)问题,也就是写入线程吃吃无法竞争到锁定而一直处于等待状态。精彩内容,尽在百度攻略:StampedLock控制锁有三种模式(写,读,乐观读),一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。所谓的乐观读模式,也就是若读的操作很多,写的操作很少的情况下,你可以乐观地认为,写入与读取同时发生几率很少,因此不悲观地使用完全的读取锁定,程序可以查看读取资料之后,是否遭到写入执行的变更,再采取后续的措施(重新读取变更信息,或者抛出异常) ,这一个小小改进,可大幅度提高程序的吞吐量!!下面是java doc提供的StampedLock一个例子精彩内容,尽在百度攻略:class Point {private double x,private final StampedLock sl = new StampedLock();精彩内容,尽在百度攻略:void move(double deltaX, double deltaY) { // an exclusively locked methodlong stamp = sl.writeLock();try {精彩内容,尽在百度攻略:x += deltaX;y += deltaY;} finally {精彩内容,尽在百度攻略:sl.unlockWrite(stamp);}}精彩内容,尽在百度攻略://下面看看乐观读锁案例double distanceFromOrigin() { // A read-only methodlong stamp = sl.tryOptimisticRead(); //获得一个乐观读锁精彩内容,尽在百度攻略:double currentX = x, currentY = //将两个字段读入本地局部变量if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其他写锁发生?stamp = sl.readLock(); //如果没有,我们再次获得一个读悲观锁精彩内容,尽在百度攻略:try {currentX = // 将两个字段读入本地局部变量currentY = // 将两个字段读入本地局部变量精彩内容,尽在百度攻略:} finally {sl.unlockRead(stamp);}精彩内容,尽在百度攻略:}return Math.sqrt(currentX * currentX + currentY * currentY);}精彩内容,尽在百度攻略://下面是悲观读锁案例void moveIfAtOrigin(double newX, double newY) { // upgrade// Could instead start with optimistic, not read mode精彩内容,尽在百度攻略:long stamp = sl.readLock();try {while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合精彩内容,尽在百度攻略:long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁if (ws != 0L) { //这是确认转为写锁是否成功stamp = //如果成功 替换票据精彩内容,尽在百度攻略:x = newX; //进行状态改变y = newY; //进行状态改变精彩内容,尽在百度攻略:}else { //如果不能成功转换为写锁sl.unlockRead(stamp); //我们显式释放读锁精彩内容,尽在百度攻略:stamp = sl.writeLock(); //显式直接进行写锁 然后再通过循环再试}}精彩内容,尽在百度攻略:} finally {sl.unlock(stamp); //释放读锁或写锁}精彩内容,尽在百度攻略:}}小结:精彩内容,尽在百度攻略:StampedLock要比ReentrantReadWriteLock更加廉价,也就是消耗比较小。StampedLock与ReadWriteLock性能对比下图是和ReadWritLock相比,在一个线程情况下,是读速度其4倍左右,写是1倍。精彩内容,尽在百度攻略:下图是六个线程情况下,读性能是其几十倍,写性能也是近10倍左右:精彩内容,尽在百度攻略:下图是吞吐量提高:总结精彩内容,尽在百度攻略:1、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定;2、ReentrantLock、ReentrantReadWriteLock,、StampedLock都是对象层面的锁定,要保证锁定一定会被释放,就必须将unLock()放到finally{}中;3、StampedLock 对吞吐量有巨大的改进,特别是在读线程越来越多的场景下;精彩内容,尽在百度攻略:4、StampedLock有一个复杂的API,对于加锁操作,很容易误用其他方法;5、当只有少量竞争者的时候,synchronized是一个很好的通用的锁实现;6、当线程增长能够预估,ReentrantLock是一个很好的通用的锁实现;精彩内容,尽在百度攻略:本文作者:benhaile

我要回帖

更多关于 stampedlock 的文章

 

随机推荐