Java 理论与实践: JDK 5.0 中更灵活、更具可伸缩性的锁定机制

发布时间:2023-04-28 09:24:46

  新的锁定类提高了同步性 —— 但现在还不能抛弃 synchronized

  java.util.concurrent.lock中的类ReentrantLock被作为 Java 语言中synchronized替代功能,它具有相同的内存语义,相同的锁定,但在竞争条件下具有更好的性能,此外,它还具有synchronized其他未提供的特征。这是否意味着我们应该忘记synchronized,转而只用ReentrantLock

  Thread可用于构建、启动和操作线程,Java 语言包括跨线程传达并发约束的结构 ——synchronized和volatilesynchronized 快速回顾

  原子性(atomicity)和可见性(visibility)。原子性意味着一个线程只能由指定的监控对象执行一次(lock)在更新共享状态时,保护代码以防止多个线程发生冲突。可见性更微妙;它需要处理内存缓存和编译器优化的各种异常行为。一般来说,线程不受缓存变量值的限制,但如果开发人员使用同步,如下面的代码所示,运行库将确保一个线程在现有变量之前更新变量synchronized块进入同一监控器时进行更新(lock)另一种保护synchronized块时,您将立即看到这些变量的更新。类似的规则也存在于volatile变量。(关于同步和 Java 内存模型的内容请参考参考资料。) synchronized (lockObject) { // update object state}

  因此,实现同步操作需要考虑安全更新多个共享变量所需的一切,不能有争议条件或损坏数据(假设同步边界位置正确),并确保这些变量的最新值可以在正确同步的其他线程中看到。定义一个清晰的跨平台内存模型(该模型在 JDK 5.0 对原定义中的一些错误进行了修改和纠正),有可能遵守以下简单规则,构建“一次写作,随处可见”的并发类别:

  无论何时,只要您将编写的变量读取到另一个线程,或者您将读取的变量最终被另一个线程写入,您就必须同步。

  但是现在好了一点,最近 JVM 在中间,没有争议的同步性能成本仍然很低(当一个线程有锁时,没有其他线程试图获得锁)。(并不总是这样;早期 JVM 同步还没有优化,所以很多人都这么认为,但现在这已经成为一种误解,人们认为同步有很高的性能成本,无论是否有效。)

  回页首 对 synchronized 的改进

  java.util.concurrent.lock ReentrantLock 类

  java.util.concurrent.lock中的Lock框架是锁定的抽象,允许锁定的实现 Java 类,而不是作为语言的特征。这就是为什么Lock各种实现留下了空间,各种实现可能有不同的调度算法、性能特征或锁定语义。ReentrantLock类实现了Lock,它拥有与synchronizedreentrant锁是什么意思?简单来说,它有一个与锁相关的获取计数器。如果锁的某个线程再次被锁定,获取计数器将添加1,然后锁需要释放两次才能真正释放。这个模仿synchronized语义;如果线程进入由线程已有的监控器保护的监控器 synchronized 当线程退出第二个(或后续)时,块允许线程继续进行synchronized块时,锁不释放,只有线程退出监控器保护的第一个synchronized只有在块的时候才释放锁。Lock 清单 1. 用 ReentrantLock 保护代码块。 Lock lock = new ReentrantLock();lock.lock();try { // update object state}finally { lock.unlock(); }

  ReentrantLock实现更多的可伸缩性。(未来 JVM 版本中,synchronized 很有可能提高竞争性能。)这意味着当许多线程使用相同的锁时,使用ReentrantLock总支出通常要比较synchronized 比较 ReentrantLock 和 synchronized 的可伸缩性

  synchronized和Lock相对可伸缩性。这个例子很好,因为每次调用nextRandom()时,PRNG 他们都在做一些工作,所以这个基准程序实际上是在测量一个合理和真实的synchronized和LockPseudoRandom它只有一种方法nextRandom(int bound)。该接口与java.util.Random类的功能非常相似。因为当生成下一个随机数时,PRNG 使用最新生成的数字作为输入,并将最终生成的数字作为实例变量进行维护,重点是使更新的代码段不被其他线程占据,所以我想使用某种形式的锁定来确保这一点。(java.util.Random类也可以做到这一点。)我们为此。PseudoRandom构建了两个实现;一个使用; syncronized,另一个使用java.util.concurrent.ReentrantLock。驱动程序生成了大量的线程,每个线程都在疯狂地争夺时间片,然后计算每秒可以执行多少轮不同版本。图片 1 和 图 2 总结了不同线程数量的结果。这个评估并不完美,只在两个系统上运行(一个是双线) Xeon 运行超线程 Linux,另一种是单处理器 Windows 但是,系统应该足以表达,但是,synchronized与ReentrantLock 图 1. synchronized 和 Lock 吞吐率,单 CPU 图 2. synchronized 和 Lock 吞吐率(标准化后),4 个 CPU

  synchronized情况。每个实现都相对快速地集中在一定稳定状态的吞吐量上,通常要求处理器充分利用,大部分时间花在实际工作上(计算机随机数),只花在线程调度费用上。你会注意到的,synchronized 在处理任何类型的争论时,版本的性能都很差,而且Lock 条件变量

  Object在线程中包含一些特殊的方法wait()、notify()和notifyAll()通信。这些都是先进的并发性特征,许多开发人员从未使用过它们 —— 这可能是一件好事,因为它们非常微妙,很容易使用不当。幸运的是,随着 JDK 5.0 中引入java.util.concurrent,开发人员几乎没有地方使用这些方法。wait或notify,你必须持有对象的锁。就像Lock同步总结是一样的,Lock框架包含对wait和notify这个概括叫做概括,称为概括条件(Condition)。Lock对象作为绑定到锁上的条件变量的工厂对象,与标准相匹配wait和notify不同的方法,对于指定的方法Lock,不止一个条件变量可以与之相关。这简化了许多并发算法的开发。例如,条件(Condition)的 Javadoc 在有界缓冲区实现的示例中,使用了两个条件变量,“not full”和“not empty",它比每一个都好 lock 只用一个 wait 实现设置的可读性更好(更有效)。Condition的方法与wait、notify和notifyAll方法相似,分别命名为await、signal和signalAll,因为它们无法覆盖Object 这不公平

  ReentrantLock构造器的参数之一是 boolean 它允许你选择公平(fair)锁,还是不公平的(unfair)锁。公平锁使线程按要求锁的顺序锁定;不公平锁允许讨价还价。在这种情况下,线程有时可以先锁定要求锁定的其他线程。falseReentrantLock这是“不公平”的。这个事实只是表面化了同步中一直是事件的东西。如果你不介意同步,那么在ReentrantLock

  图 3 和图 4 包含与图 1和图 2相同的数据只添加了一个随机数基准测试的数据集,使用公平锁,而不是默认的协商锁。正如你所看到的,公平是有代价的。如果你需要公平,你必须付出代价,但不要把它作为你的默认选择。 图 3. 使用 4 个 CPU 同步、协商锁和公平锁的相对吞吐率 图 4. 使用 1 个 CPU 同步、协商和公平锁的相对吞吐率 处处都好?

  ReentrantLock无论在哪方面都比较synchronized好 —— 所有synchronized它可以做任何你能做的事,它拥有和拥有synchronized同样的内存和并发语义,也有synchronized所没有的特性在负载下也有更好的性能。那么,我们应该忘记吗?synchronized,不再把它当作优化的好主意?或者甚至使用它ReentrantLock重写我们现有的东西synchronized代码?实际上,几本书 Java 这种方法用于编程介绍性书籍的多线程章节,并完全使用Lock 还不要抛弃 synchronized

  ReentrantLock这是一个非常感人的实现,相对而言 synchronized 它有一些重要的优势,但我认为它渴望 synchronized 视而不见,绝对是个严重的错误。java.util.concurrent.lock锁定类是用于高级用户和高级情况的工具。一般来说,除非你是对的Lockjava.util.concurrent.lock就中锁类而言,synchronized 还是有一些优点的。例如,在使用中 synchronized 不要忘记释放锁;退出时synchronized块时,JVM 会为你做的。你很容易忘记使用它。finally块释放锁对程序非常有害。您的程序可以通过测试,但在实际工作中会出现死锁,因此很难指出原因(这就是为什么初级开发人员根本不使用它LockLock类只是普通类,JVM 不知道具体哪个线程有Lock对象。而且,几乎每个开发人员都很熟悉 synchronized,它可以在 JVM 在所有版本中工作。 JDK 5.0 在成为标准(从现在开始可能需要两年)之前使用。Lock 选择何时使用 ReentrantLock 代替 synchronized

  ReentrantLock?答案很简单 —— 确实需要一些 synchronized 当没有特征时,如时间锁等待、中断锁等待、无块结构锁、多个条件变量或锁投票。ReentrantLock它还具有可伸缩性的优点,应在高度争用的情况下使用,但请记住,大多数都是可伸缩的 synchronized 块几乎从来没有争用过,所以高度争用可以放在一边。我建议使用它 synchronized 开发,直到证明 synchronized 如果使用不合适,不要只是假设ReentrantLock 结束语

  Lock框架是同步兼容替代品,提供了同步兼容替代品synchronized它没有提供很多特性,它的实现在竞争下提供了更好的性能。然而,这些明显的好处还不足以使用ReentrantLock代替synchronized原因。相反,这取决于你是否需要ReentrantLock做出选择的能力。大多数情况下,你不应该选择它 —— synchronized 如果你工作得好,你可以做所有的事情 JVM 在工作中,更多的开发人员了解它,不容易出错。只有在真正需要的时候Lock

上一篇 Spring Security教程-实现HTTP基本认证
下一篇 Java多线程(四)、线程池

文章素材均来源于网络,如有侵权,请联系管理员删除。

标签: Java教程Java基础Java编程技巧面试题Java面试题