Table of Contents
1. Chapter 1 Introduction
多线程带来的风险:
- Saftey Hazards,如race condition
- Liveness Hazards,如
- deadlock:A hold L1 wait for L2, B hold L2 wait for L1
- starvation: perpetually denied access to resources it needs in order to make progress
- livelock: not blocking but can’t make progress
- Performance Hazards,如Context Switch频繁、过度使用同步导致cache经常无效
2. Fundimentals
2.1. Chapter 2 Thread Safety
解决多线程同时访问mutable state时的三种方法:
- 改成immutable
- 不共享mutable state
- 使用同步
什么是Thread safe class
在多线程访问情况下,能正确执行- 不依赖访问线程的scheduling和interleaving
- 不需要访问线程增加额外同步机制
线程安全的
- Stateless对象
- Immutable objects
常见的race condition:
- check-then-act
- read-modify-write
synchorinzed关键字
- 又叫intrinsic locks or monitor locks
- reentrant
- 影响performane,如同一时间只能一个线程进入、清除Cache
2.2. Chapter 3 Sharing Objects
Vsisibility
在未同步情况下,compiler/processor/runtime会调整代码的执行顺序,从而导致:- 一个线程对变量A的修改,在另外一个线程不可见
- 一个线程按顺序修改变量A、B,在另外一个线程看到B先修改等
Java的Out-of-thin-air safety机制
能确保所有变量被原子的赋值,除了non-volatile的64位数值变量
Locking的作用:
- mutual exclusion
- memory visibility
volatile和locking的区别:
- locking保证visiblilty和automicity
- volatile只保证visiblilty
volatile的使用前提:
- write不依赖当前值,如设置flag就是,但increment就不是
- 变量没和其他变量一起参与到invariants
什么是publishing an object
Publishing an object means making it available to code outside of its current scope, - storing a reference to it where other code can find it (如用public的自动去引用) - returning it from a nonprivate method - passing it to alien method
什么是alien methods
- 其他类的methods
- overrideable methods (非final非private)
Escape
一个对象在不该被publish时publish了:- 违反了设计初衷
- 不安全的publish
Safe construction原则
不要让this在构造函数中escape,典型的反面例子有:- 在构造函数中,启动(而不是创建)thread
- 在构造函数中调用overrideable instance method
Thread confinement
保证data只在一个线程被访问的技术叫做Thread confinement。 有三种方法:- Ad-hoc thread confinement。靠实现和调用者约定
- Stack confinement
- ThreadLocal
什么是immutable object
- state在构造后不改变 -所有字段是final
- properly constructed(即在构造函数中没有escape this)
Java Memory Model保证immutable object的state在构造后对其他线程可见
安全的publish一个对象
为了安全publish一个对象,以下信息需要对其他线程可见:- 指向对象的reference
- 对象内部的state
如何safe publish一个properly constructed object - 类的static initalizer中初始化reference - 用volitle或AtomicREference引用 - 用final - lock
Collection的线程安全
从一个thread-safe的collection中存取object是保证可见的,这包括:- Hashtable, synchronizedMap, ConcurrentMap
- Vectork, CopyOnWriteArrayList, CopyOnWriteArraySet, synchronizedList, SynchronizedSet
- BlockingQueue, ConcurrentLinedQueue
3. Structuring Concurrent Applicatiosn
3.1. Chapter 7 Task cancellation
线程结束方法
- owner线程设置flag,target线程根据flag状态结束。这种方法不适用于线程会blocking等待事件的情况
- owner线程调用Thread.interrupt(),target线程检查Thread.isInterrupted()且捕获InterruptException
线程interrupt相关方法
- Thread.interrupt()会设置目标线程interrupted flag
- Thread.interrupted()清除 interrupted flag
- Thread.isInterrupted()返回interrupted flag
- Interruptable Blocking的操作如Thread.sleep(),如果在调用之前interrupted flag为true,也会抛InterruptedException
如下面的代码:
public class TestInterrupt {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
System.out.println("start sleep...");
try {
Thread.currentThread().interrupt();
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end sleep in ms: " + (end - start));
}
});
t.start();
try {
Thread.sleep(10000000);
} catch (Exception exception) {
System.out.println(exception);
}
}
}
输出:
start sleep...
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at TestInterrupt$1.run(TestInterrupt.java:13)
at java.lang.Thread.run(Unknown Source)
end sleep in ms: 1
3.2. Chapter 8 Applying Thread Pools

Figure 1. 计算Thread线程的个数
例如:
- 当CPU利用率为100%,Wait Time和Compute Time相同时,线程数为CPU*2
- 当CPU利用率为100%,W/C为2时,线程数为CPU*3
这个公式这样算更直观:

4. Liveness, Performane, and Testing
4.1. Chapter 11 Performance and Scalability
多线程对scalability的影响
The principal threat to scalability in concurrent applications is the exclusive resource lock.
synchornization两种结果
- uncontended如lock现在没有人占用
- contended如lock现在被人占用
uncontended时的cost
- 因visibility需要用到memory barrier,会带来:flush/invalidate caches, flush hardware write buffer, stall execution pipeline, inhibit compiler optimiztion
- 大概需要20-250 clock cycle
- Compiler或者JVM可能可以优化部分:escape analysis, lock elision(不对local的lock), lock coarsening(合并lock)
contented时的cost
- 包含uncontended时的cost
- 两次上下文切换
影响lock contection的因素,以及解决方法
因素:- 请求lock的频率
- lock持有的时间
解决方法:
- 减少频率:lock splitting(不同资源用不同的锁),lock striping(partition locking on a variable sized set of independent objects,如ConcurrentHashmap)
- 降低持有时间:narrow lock scope,
- 用其他机制代替exlusive lock: Concurrent Collection, Read/Write Lock, Immutable objects, Atomic Variables
ConcurrentMap的大概实现方式
- 将buckets分组,每组用不同的lock,put/del只需要lock相应的bucket的锁
- 计算size时,每组buckets有个counter(每次put/del都会更新),总的size根据各buckets的counter动态计算
ConcurrentHashMap和synchronized HashMap
- 单线程时,前者也更快
- 多线程时,前者Through put能随线程数平稳增长,后者不会
- 容量很小时,前者更费内存

5. Advanced Topics
5.1. Chapter 13 Explicit Locks
intrinsic lock和ReentrantLock的区别
- 后者增加try
- 后者增加interrupt
- 后者增加fairness功能
- 前者只能是block方式使用,后者可以灵活lock和unlock(但是只能当前线程unlock)
- Java 5前者性能差很多,Java 6前者性能稍微差一点
fair和nonfair
- fair:先rqurest lock的先acquire
- nonfair:barging,可以插队
性能nonfair的好很多:

fair性能差的原因
被suspended线程(因为等待lock)后被resume(因为获取lock成功),到运行(因为cpu调度到他),有很大延迟。
Let’s say thread A holds a lock and thread B asks for that lock. Since the lock is busy, B is suspended. When A releases the lock, B is resumed so it can try again. In the meantime, though, if thread C requests the lock, there is a good chance that C can acquire the lock, use it, and release it before B even finishes waking up. In this case, everyone wins: B gets the lock no later than it otherwise would have, C gets it much earlier, and throughput is improved.
read/write lock
Thoughput比renentrent lock稍微好一点,但是还是没有ConcurrentHashMap好5.2. Chapter 15 Atomic Variables and Nonblocking Synchronization
Compare and Set (CAS)
AtomicReference.compareAndSet既保证Atomicity也保证Visibility
CAS可以用来实现non-blocking算法
ABA问题
如在实现Linked List时,如果维护Node Pool,很容易有这个问题。
CAS只能解决: 变量V的值是否在我上次观察他之后变成其他不一样的值? 而有时候我们要的保证是:变量V的值是否在我上次观察他之后改变过(跟现在可能还是一样)?
ABA解决办法
- AtomicStampedReference:updates an object reference-integer pair
- AtomicMarkableReference:object reference-boolean pair
5.3. Chapter 16 Java Memory Model
Memory Model
定义在什么条件下,一个操作能看到一个变量的修改(可能来自本线程,也可以来自其他线程)
Architecture Memory Model
An architecture’s memory model tells programs what guarantees they can expect from the memory system, and specifies the special instructions required (called memory barriers or fences) to get the additional memory coordination guarantees required when sharing data
Sequential consisitency
- 操作只有一种执行顺序
- 任何读操作都能看见在他之前发生的写操作的结果
Reordering发生的原因
- 由于编译器或流水线等,导致指令执行顺序改变
- 由于cache等,导致写操作的结果可见时间变化
Happens-Before(HB)关系
要“保证”操作Y看到操作X的结果,需要Y happens-before X
happens-before关系有piggy-back特性,既: X之前的所有操作对Y之后的所有操作可见
下图,操作lock happens-before unlock:

data race
数据被多个线程读写,但是读和写操作,没有根据happens-before关系排序。 如,在指令指令时间上,顺序是这样的:- A线程写变量V
- B线程读变量V
但是如果没有机制保证A写变量V happens-before B读变量B,则视为有data race
correctly synchronized program
一个没有data race的program,所有操作按固定的(而不是会随机变化的)顺序执行。
java定义的happens-before(HB) 关系
下面的先发生指的是在指令的执行顺序- Program order rule:同线程内,先发生的action HB 后执行的action
- Monitor lock rule:对于同一个lock,先发生的unlock调用 HB 后发生的lock调用
- Volatile variable rule:对同一个volatile变量,先发生的写 HB 后发生的读
- Thread start rule:对于一个线程对象,对Thread.start的调用 HB 线程内任一指令的执行
- Thread termination rule:线程内任一指令的执行HB对该线程的Thread.join或Thread.isAlive()(仅当返回false时成立)
- Interruption rule:对一个线程的interrupt操作HB线程检查到该interrupt(如收到InterruptedException或isInterrupted返回true)
- Finalizer rule:对象构造完成 HB finalizer开始
- Transitivity. 如果A HB B且B HB C,则A HB C
piggybacking
根据happens-before的特性,A happens-before B实际上表示了所有A之前(包括A)的action对所有B之后(包括B)的操作可见。
因此可以使用piggybacking的方式,把多个happens-before关系合并成一个
Initialization safety
对于properly constructed objects,所有线程能正确的看到他的final字段,以及通过该字段所有到达的所有变量。 (such as the elements of a final array or the contents of a HashMap referenced by a final field)5.3.1. Lazy initilization singleton的实现方法及问题:
方法1
@NotThreadSafe
public class UnsafeLazyInitialization {
private static Resource resource;
public static Resource getInstance() {
if (resource == null)
resource = new Resource(); // unsafe publication
return resource;
}
}
问题: - 可能多个resource - 因为没有happens-before保证,对resource指针的赋值,可能对其他线程不可见 - 因为没有happens-before保证,对Resource构造函数的调用,可能对其他线程不可见(从而导致状态非法)
方法2
@ThreadSafe
public class SafeLazyInitialization {
private static Resource resource;
public synchronized static Resource getInstance() {
if (resource == null)
resource = new Resource();
return resource;
}
}
问题:性能,每次调用getInstance都需要获取lock
方法3
@ThreadSafe
public class EagerInitialization {
private static Resource resource = new Resource();
public static Resource getResource() { return resource; }
}
问题:没有可见性问题,但是没有lazy功能
Lazy initialization Holder class
@ThreadSafe
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
}
public static Resource getResource() {
return ResourceHolder.resource;
}
}
问题:没有问题
Double-Checked-Locking antipattern
@NotThreadSafe
public class DoubleCheckedLocking {
private static Resource resource;
public static Resource getInstance() {
if (resource == null) { // (1)
synchronized (DoubleCheckedLocking.class) { // (2)
if (resource == null)
resource = new Resource();
}
// (3)
}
return resource;
}
}
问题:下列顺序会导致问题
- 线程A进入语句(2),并执行到(3),对线程A来说resource指针已经初始化,其指向的对象的构造函数已经调用
- 线程B执行到(1),因为B和A之间没有happens-before关系,所以可能出现:第一步的resource的赋值对B可见,但对Resource构造函数的调用对B不可见
将resource设置为volatile能解决这个问题。但是仍然不如Lazy initialization Holder class性能高和易理解

0 评论:
Post a Comment