ReentrantLock

2021-01-21  本文已影响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类似

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

对于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操作获取锁

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

上一篇下一篇

猜你喜欢

热点阅读