auto commit
This commit is contained in:
parent
ef442483db
commit
9d25e88ed8
@ -44,6 +44,8 @@
|
||||
* [锁优化](#锁优化)
|
||||
* [1. 自旋锁与自适应自旋](#1-自旋锁与自适应自旋)
|
||||
* [2. 锁消除](#2-锁消除)
|
||||
* [3. 锁粗化](#3-锁粗化)
|
||||
* [4.](#4)
|
||||
* [多线程开发良好的实践](#多线程开发良好的实践)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
@ -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.
|
||||
|
||||
# 多线程开发良好的实践
|
||||
|
||||
- 给线程命名;
|
||||
|
Loading…
x
Reference in New Issue
Block a user