美文网首页
ReentrantLock

ReentrantLock

作者: 得力小泡泡 | 来源:发表于2021-01-21 10:22 被阅读0次

    1、ReentrantLock的介绍

    由AQS那一章可知:
    ReentrantLock:典型的排它锁,也就是只要有一个线程调用的lock()方法其它线程就不可能进入lock()方法中,此时state表示线程可重入的次数,因为ReentrantLock是典型的可重入锁,也就是lock()里面的代码还可以调用lock()方法,此时state值就不断的加1,而调用unlock()时则state会减1,一直减到0表示此锁彻底得到释放了,此时其它线程就可以拿到执行资源了。

    下面根据此代码对ReentrantLock进行探讨
    package com.concurrency2;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.stream.IntStream;
    
    public class MyTest9 {
        private Lock lock = new ReentrantLock();
    
        public void method() {
            try {
                lock.lock();
    
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println("method");
    
            } finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) {
            MyTest9 myTest9 = new MyTest9();
            IntStream.range(0, 10).forEach(i -> {
                new Thread(() -> {
                    myTest9.method();
                }).start();
            });
        }
    }
    

    输出

    method
    method
    method
    method
    method
    method
    method
    method
    method
    method
    

    2、ReentrantLock与AQS的关系

    image.png

    AQS使用的套路基本都是定义了一个Sync内部类,然后具体由它来实现AQS的细节

    3、公平锁与非公平锁

    1、公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。和synchronized类似

    • 优点:所有的线程都能得到资源,不会饿死在队列中。
    • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

    2、非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

    • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
    • 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

    对于ReentrantLock而言它里面有两种锁类型:公平锁与非公平锁

    公平锁的创建方式

        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
    
    Lock lock = new ReentrantLock(true);
    

    非公平锁的创建方式

        public ReentrantLock() {
            sync = new NonfairSync();
        }
    
    Lock lock = new ReentrantLock(); / /默认是非公平锁
    

    4、ReentrantLock如何通过公平锁和非公平锁进行实现

    总结:对于ReentrantLock来说,所谓的上锁,本质上就是对AQS中的state成员变量的操作;对成员变量+1,表示上锁;对成员变量-1,表示释放锁

    有对源码位置做了些调动,不过原理和意思是不便的,方便观看

    ReentrantLock类的大致结构
    public class ReentrantLock implements Lock, java.io.Serializable {
        private final Sync sync;
        abstract static class Sync extends AbstractQueuedSynchronizer {
            ...
        }
        static final class NonfairSync extends Sync {
            ...
        }
        static final class FairSync extends Sync {
            ...
        }
    
        public ReentrantLock() {
            sync = new NonfairSync();
        }
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
        public void lock() {
            sync.lock();
        }
    
        public void unlock() {
            sync.release(1);
        }
    }
    
    1、进入Sync,观察ReentrantLock有什么样具体实现
        abstract static class Sync extends AbstractQueuedSynchronizer {
            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;
                    setExclusiveOwnerThread(null);//被标识为排它锁的线程设置为空
                }
                setState(c);
                return free;
            }
    
            public final void acquire(int arg) {
                if (!tryAcquire(arg) &&
                    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                    selfInterrupt();
            }
    
            public final boolean release(int arg) {
                //如果当前没有线程持有锁
                if (tryRelease(arg)) {
                    Node h = head;
                    if (h != null && h.waitStatus != 0)
                        unparkSuccessor(h); //唤醒当前结点的下一个结点的线程
                    return true;
                }
                return false;
            }
            private void unparkSuccessor(Node node) {
                int ws = node.waitStatus;
                if (ws < 0)
                    compareAndSetWaitStatus(node, ws, 0);
    
                Node s = node.next;
                if (s == null || s.waitStatus > 0) {
                    s = null;
                    for (Node t = tail; t != null && t != node; t = t.prev)
                        if (t.waitStatus <= 0)
                            s = t;
                }
                if (s != null)
                    LockSupport.unpark(s.thread);
            }
        }
    
    2、进入FairSync,观察ReentrantLock如何实现非公平锁
        static final class FairSync extends Sync {
    
            final void lock() {
                acquire(1);
            }
    
            protected final boolean tryAcquire(int acquires) {
                final Thread current = Thread.currentThread(); 
                int c = getState(); //获取到当前state的状态
               //c == 0表示没有人在使用该锁
                if (c == 0) {
                    if (!hasQueuedPredecessors() && //判断FIFO阻塞队列是否有元素
                        compareAndSetState(0, acquires)) { //尝试进行CAS操作将state的值从0变成1
                        setExclusiveOwnerThread(current); //标记当前线程已经拿到锁
                        return true;
                    }
                }
                else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires; //锁的状态state ++
                    if (nextc < 0)
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc); 
                    return true;
                }
                return false;
            }
    
            / /此方法是从AQS类中继承过来使用的,为了方便贴在这里
            //判断能否获取到锁,如果能则state ++;不能则加入到FIFO阻塞队列中
            public final void acquire(int arg) {
                if (!tryAcquire(arg) && 
                    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                    selfInterrupt();
            }
        }
    

    ReentrantLock使用公平锁的大致实现思路是:
    1、获取锁之前会对FIFO队列进行判断
    ①:如果FIFO队列有元素,则进入到FIFO阻塞队列的末尾进行等待
    ②:如果FIFO队列没有元素,则尝试进行CAS操作尝试获取锁,获取成功则表示该线程拿到了锁;失败则进入到FIFO阻塞队列的末尾进行等待

    2、若当前线程再次获取到了锁,则state ++,标识锁的次数
    3、当锁被释放时(调用了unlock方法),那么底层会调用release方法对state成员变量值减 1 操作,如果减 1 后,state值不为0,那么release操作就执行完毕;如果减一操作后,state值为0,则调用LockSupport的unpark方法唤醒该线程后的等待队列中的第一个后继线程(pthread_mutex_unlock),将其唤醒,使之能够尝试获取到对象的锁(release时,对于公平锁和非公平锁的处理逻辑是一致的),这个时候如果是非公平锁的话,也有可能被刚来的线程抢到了锁。之所以调用release方法后state不为0,原因在于ReentrantLock是可重入锁,多次调用lock方法state值加1

    3、进入NonfairSync,观察ReentrantLock如何实现非公平锁
        static final class NonfairSync extends Sync {
    
            final void lock() {
                //在进入FIFO阻塞队列之前,判断使用通过CAS操作将state的值从0变成1,从而获取到该锁。
                //若成功,则设置标识当前线程已经拿到锁;否则进入acquire(1)
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                    acquire(1);
            }
    
            protected final boolean tryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState(); //获取到当前state的状态
                //c == 0表示没有人在使用该锁
                if (c == 0) { 
                    if (compareAndSetState(0, acquires)) { //尝试进行CAS操作将state的值从0变成1
                        setExclusiveOwnerThread(current);//标记当前线程已经拿到锁
                        return true;
                    }
                }
                else if (current == getExclusiveOwnerThread()) {
                    int nextc = c + acquires; //锁的状态state ++
                    if (nextc < 0) // overflow
                        throw new Error("Maximum lock count exceeded");
                    setState(nextc);
                    return true;
                }
                return false;
            }
    
            / /此方法是从AQS类中继承过来使用的,为了方便贴在这里
            public final void acquire(int arg) {
                if (!tryAcquire(arg) &&
                    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                    selfInterrupt();
            }
        }
    

    ReentrantLock使用非公平锁的大致实现思路是:
    1、会尝试CAS操作获取锁

    • 如果获取不到锁,会直接进入到FIFO阻塞队列中进行等待,按顺序获取锁;
    • 如果获取到了锁,则标识当前线程已经拿到锁(插队拿锁)

    2、若当前线程再次获取到了锁,则state ++,标识锁的次数
    3、当锁被释放时(调用了unlock方法),那么底层会调用release方法对state成员变量值减 1 操作,如果减 1 后,state值不为0,那么release操作就执行完毕;如果减一操作后,state值为0,则调用LockSupport的unpark方法唤醒该线程后的等待队列中的第一个后继线程(pthread_mutex_unlock),将其唤醒,使之能够尝试获取到对象的锁(release时,对于公平锁和非公平锁的处理逻辑是一致的),这个时候如果是非公平锁的话,也有可能被刚来的线程抢到了锁。之所以调用release方法后state不为0,原因在于ReentrantLock是可重入锁,多次调用lock方法state值加1

    相关文章

      网友评论

          本文标题:ReentrantLock

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