1. Lock与Synchronize区别
-
Lock是由代码实现,核心是CAS操作;synchronize则是关键字,通过修改对象头中的锁信息,由JVM实现调用。更详细的底层原理实现可见Java多线程——Lock和Synchronized底层原理比较及synchronized和lock的区别(底层实现)
-
由于Lock由代码实现,故需要在finally语句块中显示关闭锁,避免异常情况资源不释放。synchronize则会在线程崩溃时,由JVM自动释放锁资源。
-
synchronize在JDK1.5之前是重量级锁,每次都需要像操作系统申请资源,而JDK1.6后优化 synchronized 的性能给它的锁加入了四种状态,无锁状态 -> 偏向锁 -> 轻量级锁 -> 重量级锁(MarkWord储存状态中就可以看到),故后续性能和Lock差不多。
-
synchronize通常和object.wait()、object.notify()、object.notifyAll()搭配使用,没有显示的条件变量控制;Lock提供了更精细化的锁粒度,可在Lock.lock()及Lock.unlock()块中,通过Condition条件变量类搭配condition.await()、condition.signal()、condition.signalAll()使用。
-
Lock支持超时等待机制,synchronize不支持
2. Lock的引入
JDK1.5版本引入了java.util.concurrent.locks包,包含了Lock接口及其实现类。在Java已有管程实现的synchronize的基础下,作为引入Lock类的推动力,可以看看优化点:
- synchronize不支持超时等待,故在死锁原因层面,无法针对资源不可剥夺这个条件进行防范
- synchronize不支持显式的条件变量,无法更精细的控制并发粒度
- synchronize不能响应中断,Lock的lockInterruptibly()方法支持中断
- synchronize不支持获取锁失败时不进入阻塞状态,Lock的tryLock()方法支持
- 更好的性能(jdk1.6已对synchronize优化性能)
故,Java自JDK1.5版本后有了两种对于管程的实现。
3. Lock使用范例
class LockTest {
private final Lock lock = new ReentrantLock();
private int val;
public void add() {
lock.lock();
try {
val += 1;
} finally {
lock.unlock(); //必须在finally块中加unlock()操作
}
}
}
共享变量val的可见性保证分析如下。首先参考Lock相关类的类图
ReentrantLock继承自AbstractQueuedSynchronizer类,而AbstractQueuedSynchronizer类中有一个状态值state
// The synchronization status
private volatile int state;
可以看到state使用了volatile进行修饰,lock的时候会对state进行+1操作,unlock的时候则会-1,故都针对state有读写动作。利用JMM先行发生(Happens-Before)的规则(具体可参考Java内存模型:Java解决可见性和有序性问题的方案):
- 顺序性规则
针对线程A,val += 1
Happens-Before于lock.unlock()
- volatile变量规则
针对volatile变量的读,永远在写之后。那么线程A的lock.unlock()
Happens-Before于线程B的lock.lock()
操作 - 传递性规则
综合1和2的顺序,那么线程A的val += 1
Happens-Before于线程B的val += 1
当前volatile只是保证了可见性,不同线程若是同时读到最新值state值并写入,还是存在并发问题。互斥性就由lock()底层中的CAS自旋操作来保证。