auto commit

This commit is contained in:
CyC2018 2018-03-10 22:32:24 +08:00
parent ef442483db
commit 9d25e88ed8

View File

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