美文网首页
Java中的显式锁

Java中的显式锁

作者: BugBean | 来源:发表于2019-11-08 01:11 被阅读0次

    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的继承体系


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

    参考文献

    相关文章

      网友评论

          本文标题:Java中的显式锁

          本文链接:https://www.haomeiwen.com/subject/ykxgectx.html