Java中的显式锁

Lock接口
在java5之前,要实现同步只能用synchronize,在java5后,随着并发工具包的出现,出现了另一种同步方式--显式锁,显式锁提供了更丰富,粒度更细的加锁方式,其中的"锁王"就是--Lock接口.
先看看它的方法列表:

方法 介绍
lock() 获取锁,如果没有锁可用,就一直阻塞
lockInterruptibly() 获取锁,直到当前线程被中断
newCondition() 返回绑定到此锁的Condition实例(用于线程通信,使用方法详见线程通信及其工具类)
tryLock() 获取锁,若获取到立刻返回true,否则立刻返回false
tryLock(long time, TimeUnit unit) 在指定时间内获取锁,超时返回false
unlock() 释放锁
Lock使用方式一般如下:
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); //注意unlock要放在finally块内,否则出现异常锁得不到释放 }

Lock与synchronize的区别 既然有了synchronize,为什么还要Lock?我们来看看Lock与synchronize的区别:
  1. 底层实现不同:
  • synchronize是jvm支持的关键字,实现原理是同步监视器(详见...)
  • Lock的底层实现依赖AQS(详见浅析AQS)
  1. 使用方法不同
  • synchronize不用手动释放锁
  • Lock需要手动释放锁
  1. Lock比synchronize提供更丰富的加锁方法以及更细的粒度控制
  • Lock可调用lockInterruptibly实现中断等待获取锁
  • Lock可调用tryLocktryLock(long time, TimeUnit unit)实现获取锁等待时间的控制
  • Lock的实现类大部分提供了公平锁和非公平锁的实现,而synchronize是非公平的
  1. Lock可实现多个条件绑定,实现精确唤醒,而synchronize只能随机唤醒
线程八锁--显式锁的实现
先看看Lock的继承体系

Java中的显式锁
文章图片
Lock继承体系 表面上看,Lock只有可重入锁,读写锁的实现类,实际上可重入锁和读写锁都有其内部类实现公平锁和非公平锁
可重入锁与非重入锁 可重入锁又被称为递归锁.在jdk中,可重入锁显式锁的实现类是ReentrantLock
Java中的显式锁
文章图片
可重入锁类图.png ReentrantLock实现了Lock接口,且有三个内部类分别是ReentrantLock.FairSync,ReentrantLock.NonfairSync和ReentrantLock.Sync
ReentrantLock.FairSync,ReentrantLock.NonfairSync分别是ReentrantLock的公平锁实现和非公平锁实现
可重入的意思是,同一个线程可以多次获取同一个锁,举个例子
ReentrantLock lock = new ReentrantLock(); lock.lock(); try { System.out.println("第一次获取锁"); //这里重复获取了同一把锁 lock.lock(); try { System.out.println("第二次获取锁"); } finally { lock.unlock(); } } finally { lock.unlock(); }

若上述代码不是可重入锁,则会在第二次获取锁时,发生死锁,因为在等待第一次获取的锁释放,而第一次获取的锁要在第二次获取锁后才释放
jdk中所有的锁都是可重入锁,包括synchronized
那么我们再来看看,可重入锁是怎么实现的
public void lock() { sync.lock(); }

可以看到是调用了内部类ReentrantLock.Sync的lock(),而ReentrantLock.Sync又有公平锁和非公平锁实现,我们看看公平锁实现
final void lock() { acquire(1); }

这个acquire()正是AQS的方法,不了解AQS的同学可以看看浅析AQS,继续往里深挖
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }

可以看到在acquire方法里首先执行tryAcquire方法,这个方法需要子类覆盖否则直接抛异常,所以我们要看的是ReentrantLock.FairSync里的tryAcquire方法
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); //调用AQS的getState方法获取线程状态 int c = getState(); //若线程状态为0,表示当前线程还没有获取锁 if (c == 0) { if (/** 此方法是查看有没有其他线程排在自己签名 **/ !hasQueuedPredecessors() && /**CAS设置state为1,回溯前面代码,传的值是1**/ compareAndSetState(0, acquires)) { //设置当前线程拥有独占访问权 setExclusiveOwnerThread(current); return true; } } //若当前线程已拥有独占访问权 else if (current == getExclusiveOwnerThread()) { //使state++,这里就是重入锁的关键 int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }

上面这段代码大概意思就是:当前线程如果还未获取锁,那就尝试获取(查看有无线程排在自己前面),若果已获取了锁,就让state++.所以state=0时,线程无锁,state>0时,state的值就表示该线程获取重入锁的次数.同理,若释放重入锁,state--
//ReentrantLock类 public void unlock() { sync.release(1); } //AQS类 public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); //唤醒后继线程 return true; } return false; } //ReentrantLock.Sync类 protected final boolean tryRelease(int releases) { int c = getState() - releases; //重入计数减1 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) {//若state为0,则释放锁 free = true; setExclusiveOwnerThread(null); } setState(c); return free; }

公平锁与非公平锁 jdk里的公平锁与非公平锁不是一个具体类,而是一种锁的公平实现与非公平实现,回顾一下可重入锁的类图
Java中的显式锁
文章图片
可重入锁类图.png 【Java中的显式锁】ReentrantLock.FairSync,ReentrantLock.NonfairSync就是ReentrantLock的公平实现和非公平实现
公平锁与非公平锁之间的区别就是在获取锁的时候,非公平锁会先尝试"插队","插队"失败就和公平锁一样排队等待,但在每次有机会获取锁时,非公平锁都会尝试"插队"
//公平锁实现 final void lock() { acquire(1); } //非公平锁实现 final void lock() { if (compareAndSetState(0, 1)) //插队 setExclusiveOwnerThread(Thread.currentThread()); //若插队成功,则赋予当前线程访问权 else acquire(1); }public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }//公平版tryAcquire protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (//区别在于这里,公平版会判断有无前继,若有前继还得排队 !hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }//非公平版tryAcquire protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (//非公平版这里没有前继判断 compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }

参考文献
  • 不可不说的Java“锁”事

    推荐阅读