Lock与Condition

发布时间 2023-11-28 17:22:38作者: kiper

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类的推动力,可以看看优化点:

  1. synchronize不支持超时等待,故在死锁原因层面,无法针对资源不可剥夺这个条件进行防范
  2. synchronize不支持显式的条件变量,无法更精细的控制并发粒度
  3. synchronize不能响应中断,Lock的lockInterruptibly()方法支持中断
  4. synchronize不支持获取锁失败时不进入阻塞状态,Lock的tryLock()方法支持
  5. 更好的性能(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相关类的类图
image

ReentrantLock继承自AbstractQueuedSynchronizer类,而AbstractQueuedSynchronizer类中有一个状态值state

// The synchronization status
private volatile int state;

可以看到state使用了volatile进行修饰,lock的时候会对state进行+1操作,unlock的时候则会-1,故都针对state有读写动作。利用JMM先行发生(Happens-Before)的规则(具体可参考Java内存模型:Java解决可见性和有序性问题的方案):

  1. 顺序性规则
    针对线程A,val += 1Happens-Before于lock.unlock()
  2. volatile变量规则
    针对volatile变量的读,永远在写之后。那么线程A的lock.unlock()Happens-Before于线程B的lock.lock()操作
  3. 传递性规则
    综合1和2的顺序,那么线程A的val += 1Happens-Before于线程B的val += 1

当前volatile只是保证了可见性,不同线程若是同时读到最新值state值并写入,还是存在并发问题。互斥性就由lock()底层中的CAS自旋操作来保证。