08-Java多线程-2、锁(显式锁ReentrantLock)

显式锁

  • synchronized我们称之为隐式锁,这篇文章我们来看看显示锁Lock

一、锁的分类和概念

1.1 可重入锁

  • 获取锁之后,还能够再次获取同一把锁,则为可重入锁

1.2 公平锁和非公平锁

  • 在时间上,先对锁进行获取的请求,一定先被满足,这个锁就是公平的,反之就是非公平的,非公平的效率一般来讲更高。因为按照公平原则,需要被调度的线程可能被操作系统挂起,如果要调度它的话需要将该线程解挂,但是非公平锁的话,就会选择一个还在内存中的线程,后面的过程比前面的过程快很多,这也是两种锁效率差别的主要原因。在AQS中,公平锁的实现里面,需要判断自己是否有前驱节点,如果有,那么必须老老实实等待前面的线程先执行,而前面的线程可能被park了,唤醒需要消耗更多的时间,因此损失了效率。
PS:
挂起:一般是主动的,由系统或程序发出,甚至于辅存中去。(不释放CPU,可能释放内存,放在外存)
阻塞:一般是被动的,在抢占资源中得不到资源,被动的挂起在内存,等待某种资源或信号量(即有了资源)将他唤醒。(释放CPU,不释放内存)

1.3 读写锁

  • 同一时间允许多个读线程访问,写线程访问时,需要阻塞其余全部的读写线程。简单表示就是读读共享,读写互斥,写写互斥,适合读多写少的应用场景。

1.4 排它锁

  • 排它锁:同一时间锁只能被一个线程获取

二、Lock接口

  • Lock接口定义了锁的基本API,如下:
lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
  • Lock接口的主要实现类是ReentrantLock,它是可重入锁。除此之外还有一些实现类,比如ConcurrentHashMap里面的Segment,读写锁内部持有的读锁和写锁等,如下:

[外链图片转存失败(img-WOYwOlAj-1565595463130)(https://note.youdao.com/yws/api/personal/file/72F33B011531428F8EB6215E36AF41A1?method=download&shareKey=2b711d4864b319d8f7d7b8369ae9634a)]
[外链图片转存失败(img-c18GA32f-1565595463131)(https://note.youdao.com/yws/api/personal/file/107A00F084774496A6F06272003B6AF9?method=download&shareKey=09a04ab50ef5b3176eee5e5da44c426f)]

三、可重入锁ReentrantLock

  • ReentrantLock实现了Lock接口,是可重入锁。我们先了解它,然后分析其源码,要点如下:
1.ReentrantLock实现了Lock接口,具备锁的功能,它支持公平锁和非公平锁2中模式,默认是非公平锁。
2.ReentrantLock内部的锁机制是基于AQS来实现的,内部的Sync类继承了AQS,然后公平和非公平2中模式对应着Sync的2个子类。
3.ReentrantLock的加锁和释放锁机制是基于AQS的state变量来做的,因为是可重入锁,还可以判断当前线程获取了多少次锁,实际上就是stste的值来代表加锁的次数
  • PS:前面2点是不是和Semaphore非常类似,Semaphore内部也是这样的模式,其实Semaphore也是一种同步工具,不过没有锁那么灵活。在Semaphore中state代表许可的个数,在ReentrantLock中state代表加锁的次数,有相似之处的,这一切都是基于AQS来做的。可以参考并发工具类之Semaphore

3.1 Sync

  • 源码
/**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     * 控制锁的同步基础,子类包括公平和非公平2中版本,使用AQS的state来代表当前
     * 锁的加锁次数
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         * 加锁的方法,交给子类实现,因为2中模式下加锁的逻辑有所不同,交给子类实现的
         * 主要原因是非公平锁可以快速尝试获取锁。
         * 我们看到2中模式下lock方法的实现区别在于非公平模式会尝试获取一次锁,获取失败,
         * 再去阻塞获取,因此Sync类里面没有实现这个方法,而是交给子类实现
         */
        abstract void lock();

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         * 非公平模式下的nonfairTryAcquire方法实现
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //如果当前线程没有加锁,CAS直接加锁
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    //加锁成功就返回
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果线程已经被加锁,并且当前线程就是尺有锁的线程,那就直接操作state,此时不需要CAS,
            // 因为只有一个线程持有锁,直接操作即可
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //如果已经加锁了,并且不是自己加锁的,那说明尝试获取state失败,返回false
            return false;
        }

        //释放锁
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            //只有持有锁的线程才能释放锁,如果本线程不是持有锁的线程,那么释放锁的操作直接抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                //说明锁已经释放了,占据锁的线程置为null
                setExclusiveOwnerThread(null);
            }
            setState(c);//设置state为0
            //返回的布尔值代表当前的state是否为0,也就是说锁已经释放了返回true,如果没有完
            // 全释放返回false(因为可重入,释放后state不为0,锁就没有完全释放)
            return free;
        }
        //判断是不是当前线程持有锁
        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        // Methods relayed from outer class
        //获取持有锁的线程,如果锁没有被持有返回null
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        //获取加锁次数
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        //是否已经被加锁,如果没有state是0
        final boolean isLocked() {
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         * 从流重构实例,反序列化(什么场景下使用呢?)
         */
        private void readObject(java.io.ObjectInputStream s)
                throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

3.2 NonfairSync

static final class NonfairSync extends java.util.concurrent.locks.ReentrantLock.Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         * 非公平模式先尝试加锁,失败之后再阻塞获取锁,因为不需要保证公平,只要能拿到,就直接获取锁
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

3.3 FairSync

static final class FairSync extends java.util.concurrent.locks.ReentrantLock.Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        //公平模式阻塞获取锁,保证公平
        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         * 公平模式的tryAcquire
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //1.如果没有加锁,并且自己没有前驱节点,并且获取锁成功,那么就返回true
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                        compareAndSetState(0, acquires)) {
                    //设置当前线程为占有锁的线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //2.如果当前线程就是持有锁的线程,那就直接操作state,注意操作后的state不能为负数
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                //修改state
                setState(nextc);
                return true;
            }
            //如果线程持有锁,在前面的else if已经返回
            //如果线程不持有锁,并且c=0,并且设置锁状态成功,第一个if返回
            //如果线程不持有锁,并且c>0,或者c=0但是设置锁失败,就会走到这个逻辑,直接返回false,表示获取锁失败了
            return false;
        }
    }

3.4 构造方法

  • 构造方法通过参数指定锁的公平模式
public ReentrantLock() {
        sync = new java.util.concurrent.locks.ReentrantLock.NonfairSync();
    }

public ReentrantLock(boolean fair) {
        sync = fair ? new java.util.concurrent.locks.ReentrantLock.FairSync() : new java.util.concurrent.locks.ReentrantLock.NonfairSync();
    }

3.5 加锁方法

3.5.1 lock()
  • lock方法直接调用Sync实例的lock方法,这个方法是在NonfairSync和FairSync里面实现的
public void lock() {
        sync.lock();
    }
3.5.2 lockInterruptibly()
  • 支持中断的加锁方法,这个方法是在AQS中实现的,AQS基于tryAcquire实现了支持中断的加锁方法
public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
3.5.3 tryLock()
  • 尝试获取锁。我们看到尝试获取锁是非公平的
public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
3.5.4 tryLock(long timeout, TimeUnit unit)
  • 支持超时的尝试获取
public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

3.6 解锁方法

3.6.1 unlock()
  • 使用Sync的release方法
public void unlock() {
        sync.release(1);
    }

3.7 其他方法

3.7.1 newCondition()
  • 获取锁的Condition对象
public Condition newCondition() {
        return sync.newCondition();
    }
3.7.2 getHoldCount()
  • 获取锁的加锁次数
public int getHoldCount() {
        return sync.getHoldCount();
    }
3.7.3 isHeldByCurrentThread()
  • 判断锁是否被当前线程持有
public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }
3.7.4 isLocked()
  • 判断是否被加锁
public boolean isLocked() {
        return sync.isLocked();
    }
3.7.5 isFair()
  • 判断是否为公平模式
public final boolean isFair() {
        return sync instanceof java.util.concurrent.locks.ReentrantLock.FairSync;
    }
3.7.6 等待线程相关
  • hasQueuedThreads():判断是否有线程在等待锁
  • hasQueuedThread(Thread thread):判断指定线程是否在等待锁
  • getQueueLength():获取排队线程的长度的估计值
  • hasWaiters(Condition condition):判断是否有线程在等待某个Condition
  • getWaitQueueLength(Condition condition):获取等待某个Condition的线程队列个数的估计值

四、小结

  • 本文我们主要是了解ReentrantLock的整体实现原理和思想,它支持公平非公平2中模式,内部持有AQS的子类Sync,然后由Sync派生2个子类来对应这2中模式。不管那种模式,加锁解锁这些操作底层都是操作同步状态state变量,由于AQS已经将线程同步已经很多固定的逻辑流程实现了,因此锁中并没有过于复杂的代码。
  • 我们看到AQS在很多线程同步的工具中出现,包括CountDownLatch,Semaphore和本文的ReentrantLock,我们后续还会看到很多基于AQS的线程工具类,也可以自己基于AQS实现一个锁,后面也会总结AQS在整个java并发体系中的核心地位。
  • 后面我们会了解另一种显示锁,ReentrantReadWriteLock读写锁。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值