diff --git a/notes/Java 并发.md b/notes/Java 并发.md index 7a40d10c..a6eeadf2 100644 --- a/notes/Java 并发.md +++ b/notes/Java 并发.md @@ -44,6 +44,8 @@ * [锁优化](#锁优化) * [1. 自旋锁与自适应自旋](#1-自旋锁与自适应自旋) * [2. 锁消除](#2-锁消除) + * [3. 锁粗化](#3-锁粗化) + * [4.](#4) * [多线程开发良好的实践](#多线程开发良好的实践) * [参考资料](#参考资料) @@ -719,7 +721,7 @@ Thread printThread = new Thread(new Runnable() { 这段 20 个线程自增 10000 次的代码使用了 AtomicInteger 之后程序输出了正确结果,一切都要归功于 incrementAndGet() 方法的原子性。 -代码清单:Atomic 的原子自增运算 +代码清单 4:Atomic 的原子自增运算 ```java /** @@ -765,7 +767,7 @@ public class AtomicTest { incrementAndGet() 的实现其实非常简单。 -代码清单:incrementAndGet() 方法的 JDK 源码 +代码清单 5:incrementAndGet() 方法的 JDK 源码 ```java /** @@ -818,7 +820,7 @@ Java 语言中,如果一个变量要被多线程访问,可以使用 volatile 也许读者会有疑问,变量是否逃逸,对于虚拟机来说需要使用数据流分析来确定,但是程序自己应该是很清楚的,怎么会在明知道不存在数据争用的情况下要求同步呢?答案是有许多同步措施并不是程序员自己加入的。同步的代码在 Java 程序中的普遍程度也许超过了大部分读者的想象。下面段非常简单的代码仅仅是输出 3 个字符串相加的结果,无论是源码字面上还是程序语义上都没有同步。 -代码清单:一段看起来没有同步的代码 +代码清单 6:一段看起来没有同步的代码 ```java public static String concatString(String s1, String s2, String s3) { @@ -828,7 +830,7 @@ public static String concatString(String s1, String s2, String s3) { 我们也知道,由于 String 是一个不可变的类,对字符串的连接操作总是通过生成新的 String 对象来进行的,因此 Javac 编译器会对 String 连接做自动优化。在 JDK 1.5 之前,会转化为 StringBuffer 对象的连续 append() 操作,在 JDK 1.5 及以后的版本中,会转化为 StringBuilder 对象的连续 append() 操作,即上面的代码可能会变成下面的样子: -代码清单:Javac 转化后的字符串连接操作 +代码清单 7:Javac 转化后的字符串连接操作 ```java public static String concatString(String s1, String s2, String s3) { @@ -841,6 +843,16 @@ public static String concatString(String s1, String s2, String s3) { ``` 每个 StringBuffer.append() 方法中都有一个同步块,锁就是 sb 对象。虚拟机观察变量 sb,很快就会发现它的动态作用域被限制在 concatString() 方法内部。也就是说,sb 的所有引用永远不会 “逃逸” 到 concatString() 方法之外,其他线程无法访问到它,因此,虽然这里有锁,但是可以被安全地消除掉,在即时编译之后,这段代码就会忽略掉所有的同步而直接执行了。 +## 3. 锁粗化 + +原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小——只在共享数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快拿到锁。 + +大部分情况下,上面的原则都是正确的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。 + +代码清单 7 中连续的 append() 方法就属于这类情况。如果虚拟机探测到由这样的一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部,以代码清单 7 为例,就是扩展到第一个 append() 操作之前直至最后一个 append() 操作之后,这样只需要加锁一次就可以了。 + +## 4. + # 多线程开发良好的实践 - 给线程命名;