Java中的显式锁

2019-11-08  本文已影响0人  BugBean

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. 底层实现不同:
  1. 使用方法不同
  1. Lock比synchronize提供更丰富的加锁方法以及更细的粒度控制
  1. Lock可实现多个条件绑定,实现精确唤醒,而synchronize只能随机唤醒

线程八锁--显式锁的实现

先看看Lock的继承体系


Lock继承体系

表面上看,Lock只有可重入锁,读写锁的实现类,实际上可重入锁和读写锁都有其内部类实现公平锁和非公平锁

可重入锁与非重入锁

可重入锁又被称为递归锁.在jdk中,可重入锁显式锁的实现类是ReentrantLock

可重入锁类图.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里的公平锁与非公平锁不是一个具体类,而是一种锁的公平实现与非公平实现,回顾一下可重入锁的类图

可重入锁类图.png

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;
}

参考文献

上一篇下一篇

猜你喜欢

热点阅读