电子商务网站建设实训心得体会,网站宣传用了最字,网站的技术支持,简历在线制作免费导出2019独角兽企业重金招聘Python工程师标准 多线程和并发性并不是什么新内容#xff0c;但是 Java 语言设计中的创新之一就是#xff0c;它是第一个直接把跨平台线程模型和正规的内存模型集成到语言中的主流语言。核心类库包含一个 Thread 类#xff0c;可以用它… 2019独角兽企业重金招聘Python工程师标准 多线程和并发性并不是什么新内容但是 Java 语言设计中的创新之一就是它是第一个直接把跨平台线程模型和正规的内存模型集成到语言中的主流语言。核心类库包含一个 Thread 类可以用它来构建、启动和操纵线程Java 语言包括了跨线程传达并发性约束的构造 —— synchronized 和 volatile 。在简化与平台无关的并发类的开发的同时它决没有使并发类的编写工作变得更繁琐只是使它变得更容易了。 synchronized 快速回顾 把代码块声明为 synchronized有两个重要后果通常是指该代码具有 原子性atomicity和 可见性visibility。原子性意味着一个线程一次只能执行由一个指定监控对象lock保护的代码从而防止多个线程在更新共享状态时相互冲突。可见性则更为微妙它要对付内存缓存和编译器优化的各种反常行为。一般来说线程以某种不必让其他线程立即可以看到的方式不管这些线程在寄存器中、在处理器特定的缓存中还是通过指令重排或者其他编译器优化不受缓存变量值的约束但是如果开发人员使用了同步如下面的代码所示那么运行库将确保某一线程对变量所做的更新先于对现有 synchronized 块所进行的更新当进入由同一监控器lock保护的另一个 synchronized 块时将立刻可以看到这些对变量所做的更新。类似的规则也存在于 volatile 变量上。 synchronized (lockObject) { // update object state
} 所以实现同步操作需要考虑安全更新多个共享变量所需的一切不能有争用条件不能破坏数据假设同步的边界位置正确而且要保证正确同步的其他线程可以看到这些变量的最新值。通过定义一个清晰的、跨平台的内存模型该模型在 JDK 5.0 中做了修改改正了原来定义中的某些错误通过遵守下面这个简单规则构建“一次编写随处运行”的并发类是有可能的 不论什么时候只要您将编写的变量接下来可能被另一个线程读取或者您将读取的变量最后是被另一个线程写入的那么您必须进行同步。 不过现在好了一点在最近的 JVM 中没有争用的同步一个线程拥有锁的时候没有其他线程企图获得锁的性能成本还是很低的。也不总是这样早期 JVM 中的同步还没有优化所以让很多人都这样认为但是现在这变成了一种误解人们认为不管是不是争用同步都有很高的性能成本。 对 synchronized 的改进 如此看来同步相当好了是么那么为什么 JSR 166 小组花了这么多时间来开发 java.util.concurrent.lock 框架呢答案很简单同步是不错但它并不完美。它有一些功能性的限制 —— 它无法中断一个正在等候获得锁的线程也无法通过投票得到锁如果不想等下去也就没法得到锁。同步还要求锁的释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行多数情况下这没问题而且与异常处理交互得很好但是确实存在一些非块结构的锁定更合适的情况。 ReentrantLock 类 java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象它允许把锁定的实现作为 Java 类而不是作为语言的特性来实现。这就为Lock 的多种实现留下了空间各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock 它拥有与 synchronized 相同的并发性和内存语义但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外它还提供了在激烈争用情况下更佳的性能。换句话说当许多线程都想访问共享资源时JVM 可以花更少的时候来调度线程把更多时间用在执行线程上。 reentrant 锁意味着什么呢简单来说它有一个与锁相关的获取计数器如果拥有锁的某个线程再次得到锁那么获取计数器就加1然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义如果线程进入由线程已经拥有的监控器保护的 synchronized 块就允许线程继续进行当线程退出第二个或者后续 synchronized 块的时候不释放锁只有线程退出它进入的监控器保护的第一个 synchronized 块时才释放锁。 在查看清单 1 中的代码示例时可以看到 Lock 和 synchronized 有一点明显的区别 —— lock 必须在 finally 块中释放。否则如果受保护的代码将抛出异常锁就有可能永远得不到释放这一点区别看起来可能没什么但是实际上它极为重要。忘记在 finally 块中释放锁可能会在程序中留下一个定时炸弹当有一天炸弹爆炸时您要花费很大力气才有找到源头在哪。而使用同步JVM 将确保锁会获得自动释放。 清单 1. 用 ReentrantLock 保护代码块。 Lock lock new ReentrantLock();
lock.lock();
try { // update object state
}
finally {lock.unlock();
} 除此之外与目前的 synchronized 实现相比争用下的 ReentrantLock 实现更具可伸缩性。在未来的 JVM 版本中synchronized 的争用性能很有可能会获得提高。这意味着当许多线程都在争用同一个锁时使用 ReentrantLock 的总体开支通常要比 synchronized少得多。 比较 ReentrantLock 和 synchronized 的可伸缩性 Tim Peierls 用一个简单的线性全等伪随机数生成器PRNG构建了一个简单的评测用它来测量 synchronized 和 Lock 之间相对的可伸缩性。这个示例很好因为每次调用 nextRandom() 时PRNG 都确实在做一些工作所以这个基准程序实际上是在测量一个合理的、真实的 synchronized 和 Lock 应用程序而不是测试纯粹纸上谈兵或者什么也不做的代码就像许多所谓的基准程序一样。 在这个基准程序中有一个 PseudoRandom 的接口它只有一个方法 nextRandom(int bound) 。该接口与 java.util.Random 类的功能非常类似。因为在生成下一个随机数时PRNG 用最新生成的数字作为输入而且把最后生成的数字作为一个实例变量来维护其重点在于让更新这个状态的代码段不被其他线程抢占所以我要用某种形式的锁定来确保这一点。 java.util.Random 类也可以做到这点。我们为 PseudoRandom 构建了两个实现一个使用 syncronized另一个使用 java.util.concurrent.ReentrantLock 。驱动程序生成了大量线程每个线程都疯狂地争夺时间片然后计算不同版本每秒能执行多少轮。图 1 和 图 2 总结了不同线程数量的结果。这个评测并不完美而且只在两个系统上运行了一个是双 Xeon 运行超线程 Linux另一个是单处理器 Windows 系统但是应当足以表现 synchronized 与 ReentrantLock 相比所具有的伸缩性优势了。 图 1 和图 2 中的图表以每秒调用数为单位显示了吞吐率把不同的实现调整到 1 线程 synchronized 的情况。每个实现都相对迅速地集中在某个稳定状态的吞吐率上该状态通常要求处理器得到充分利用把大多数的处理器时间都花在处理实际工作计算机随机数上只有小部分时间花在了线程调度开支上。您会注意到synchronized 版本在处理任何类型的争用时表现都相当差而 Lock 版本在调度的开支上花的时间相当少从而为更高的吞吐率留下空间实现了更有效的 CPU 利用。 条件变量 根类 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 上的对应方法。 这不公平 如果查看 Javadoc您会看到 ReentrantLock 构造器的一个参数是 boolean 值它允许您选择想要一个 公平fair锁还是一个 不公平unfair锁。公平锁使线程按照请求锁的顺序依次获得锁而不公平锁则允许讨价还价在这种情况下线程有时可以比先请求锁的其他线程先得到锁。 为什么我们不让所有的锁都公平呢毕竟公平是好事不公平是不好的不是吗当孩子们想要一个决定时总会叫嚷“这不公平”。我们认为公平非常重要孩子们也知道。在现实中公平保证了锁是非常健壮的锁有很大的性能成本。要确保公平所需要的记帐bookkeeping和同步就意味着被争夺的公平锁要比不公平锁的吞吐率更低。作为默认设置应当把公平设置为 false 除非公平对您的算法至关重要需要严格按照线程排队的顺序对其进行服务。 那么同步又如何呢内置的监控器锁是公平的吗答案令许多人感到大吃一惊它们是不公平的而且永远都是不公平的。但是没有人抱怨过线程饥渴因为 JVM 保证了所有线程最终都会得到它们所等候的锁。确保统计上的公平性对多数情况来说这就已经足够了而这花费的成本则要比绝对的公平保证的低得多。所以默认情况下 ReentrantLock 是“不公平”的这一事实只是把同步中一直是事件的东西表面化而已。如果您在同步的时候并不介意这一点那么在 ReentrantLock 时也不必为它担心。 图 3 和图 4 包含与 图 1和 图 2 相同的数据只是添加了一个数据集用来进行随机数基准检测这次检测使用了公平锁而不是默认的协商锁。正如您能看到的公平是有代价的。如果您需要公平就必须付出代价但是请不要把它作为您的默认选择。 处处都好 看起来 ReentrantLock 无论在哪方面都比 synchronized 好 —— 所有 synchronized 能做的它都能做它拥有与 synchronized 相同的内存和并发性语义还拥有 synchronized 所没有的特性在负荷下还拥有更好的性能。那么我们是不是应当忘记 synchronized 不再把它当作已经已经得到优化的好主意呢或者甚至用 ReentrantLock 重写我们现有的 synchronized 代码实际上几本 Java 编程方面介绍性的书籍在它们多线程的章节中就采用了这种方法完全用 Lock 来做示例只把 synchronized 当作历史。但我觉得这是把好事做得太过了。 还不要抛弃 synchronized 虽然 ReentrantLock 是个非常动人的实现相对 synchronized 来说它有一些重要的优势但是我认为急于把 synchronized 视若敝屣绝对是个严重的错误。 java.util.concurrent.lock 中的锁定类是用于高级用户和高级情况的工具 。一般来说除非您对 Lock 的某个高级特性有明确的需要或者有明确的证据而不是仅仅是怀疑表明在特定情况下同步已经成为可伸缩性的瓶颈否则还是应当继续使用 synchronized。 为什么我在一个显然“更好的”实现的使用上主张保守呢因为对于 java.util.concurrent.lock 中的锁定类来说synchronized 仍然有一些优势。比如在使用 synchronized 的时候不能忘记释放锁在退出 synchronized 块时JVM 会为您做这件事。您很容易忘记用 finally 块释放锁这对程序非常有害。您的程序能够通过测试但会在实际工作中出现死锁那时会很难指出原因这也是为什么根本不让初级开发人员使用 Lock 的一个好理由。 另一个原因是因为当 JVM 用 synchronized 管理锁定请求和释放时JVM 在生成线程转储时能够包括锁定信息。这些对调试非常有价值因为它们能标识死锁或者其他异常行为的来源。 Lock 类只是普通的类JVM 不知道具体哪个线程拥有 Lock 对象。而且几乎每个开发人员都熟悉 synchronized它可以在 JVM 的所有版本中工作。在 JDK 5.0 成为标准从现在开始可能需要两年之前使用 Lock类将意味着要利用的特性不是每个 JVM 都有的而且不是每个开发人员都熟悉的。 什么时候选择用 ReentrantLock 代替 synchronized 既然如此我们什么时候才应该使用 ReentrantLock 呢答案非常简单 —— 在确实需要一些 synchronized 所没有的特性的时候比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。 ReentrantLock 还具有可伸缩性的好处应当在高度争用的情况下使用它但是请记住大多数 synchronized 块几乎从来没有出现过争用所以可以把高度争用放在一边。我建议用 synchronized 开发直到确实证明 synchronized 不合适而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住这些是供高级用户使用的高级工具。而且真正的高级用户喜欢选择能够找到的最简单工具直到他们认为简单的工具不适用为止。。一如既往首先要把事情做好然后再考虑是不是有必要做得更快 转载于:https://my.oschina.net/u/4008390/blog/3014029