10.3 Locks and Synchronization锁与同步
10.3 Locks and Synchronization锁与同步
§ 1.Synchronization
§ 2.Locking
§ 3.Atomic operations
§ 4.Liveness: deadlock, starvation and livelock
§ 5.wait(), notify(), and notifyAll()
1.Synchronization 同步
使用锁机制,获得对数据的独家 mutation 权,其他线程被阻塞,不得访问
锁的两种操作:acquire(使一个线程取得对锁的所有权);release(释放锁的使用权)
2 Locking
①Lock是Java语言提供的内嵌机制
方法:通过synchronized 陈述取得一段代码的锁
拥有lock的线程可独占式的执行该部分代码,Lock保护共享数据
②Monitor 模式 --ADT自己作lock
任何一个简单的方法都保证锁,任何方法都是互斥的,只准调用类的一个方法
方法:
1)synchronized(this) 保证这个类的所有rep都被lock,方法体全被包含
2)方法前加synchronized
PS:构造方法前不允许synchronized,JAVA内部保证每个new操作都是原子的
最好不要随便用synchronized,同步机制对性能带来巨大影响
PS:在静态方法前家synchronized相当于给整个类加锁,对性能带来极大损耗
③对同一个 mutable 对象的操作,必须要在各线程里用synchronized 全部保护起来, 任何共享的mutable的变量必须被lock保护
④happens before关系(在重排序优化中的原则)
用来保证前一个操作的执行结果必须对后一个操作可见,用来保证内存一致性
定义:部分规则如下
1)程序顺序规则:一个线程中的每个操作happens-before于该线程中的任意后续操作
2)对于一个监视器的解锁,happens-before于随后对这个监视器的加锁
3 Atomic operations原子操作
利用关键字volatile减少内存不一致发生的风险。
volatile变量所有的写操作都和随后的操作保证了一个happens-before的关系
这意味着对volatile变量的改变总是对其他变量可见的
volatile不能解决所有问题
synchronized相当于把多个atomic operations 组合成更大的amomic operations
4 Liveness: deadlock, starvation and livelock活性:死锁,饥饿,活锁
(1)Dead lock
死锁:多个线程竞争 lock,相互等待对方释放lock(程序就卡死在这里了)
死锁的特征是循环依赖,都在等对方
死锁发生在一个线程拥有多个锁的时候
解决方法:
1) lock ordering给锁排序
PS:首先这不是模块化的,我们需要知道所有的锁;其次对于代码很难知道哪个锁是先被使用的,需要一些计算
2 ) coarse-grained locking就用一个锁(对性能是很大的影响)
(2)starvation饥饿
因为其他线程 lock 时间太长,一个线程长时间无法获取其所需的资源访问权 (lock) ,导致无法往下进行
(3) Livelock活锁
1)一个线程等待另一个线程回复,另一个线程又等待下一个线程回复,活锁就产生了
2)活锁也会导致线程无法进行,只是不是被阻塞了而是忙于回复对方从而继续工作。
5 wait(),notify(),and notifyAll()
(1)
保护块:某些条件未得到满足,所以一直在空循环检测,直到条件被满足。这是极大浪费。
对于任意的类o,
o.wait():释放o的lock,进入o的等待队列,等待
o.notify():在o 的等待队列中唤醒一个线程
o.notifyAll():唤醒o的等待队列中的所有线程
(2)o.wait()
该操作使object所处的当前线程进入阻塞/等待状态,直到其他线程调用该对象的notify()操作
o.notify()/notifyAll()
随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态
PS:这些方法必须在o被lock的时候调用,如下
(3)锁,同步与这些方法之间的关系