Java ReentrantLock 锁

发布于 2024-06-23 14:19:21 字数 7117 浏览 9 评论 0

可重复入的独占锁。可重入意味着 state 的值不只是 1,可能是 2。因为锁的拥有线程可以继续重入获得锁。

ReentrantLock 内部持有 Sync 类,NonfairSync 和 FairSync 继承 Sync 类。

源码解析

构造函数

    public ReentrantLock() {
        sync = new NonfairSync();
    }

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

无参构造默认是非公平锁,有参数的构造函数根据参数决定。

获取锁

lock()

    public void lock() {
        sync.lock();
    }

内部实现是 sync.lock。

该抽象方法在公平锁和非公平锁的实现不一样。

NonfairSync

/**
* 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);
}

acquire(1); ​是获取锁失败的 fallback 方法。调用 AQS 的 acquire 方法。

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {//1
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//2
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

第一个 if 分支表示其他线程也还没获得锁,state 为 0,当前线程可以顺利获得锁,设置锁拥有者。

else if 分支表示当前线程已经拥有该锁,重入获得该锁,更新 state 的值为 state+acquires。

否则,直接返回失败,请求线程会被放入 AQS 阻塞队列。

思考一下:非公平在代码中体现在哪?

2、假设线程 1 先在这里尝试获取锁,发现该锁被其他线程占用,于是返回 false。

然后,线程 2 尝试获取锁,发现此时锁没有被占用,于是它可以执行//1 这个分支获取锁。

所以,先到不一定先得。关键在于线程获取锁时该锁究竟有没空闲


FairSync

公平锁的关键在于

/**
* Fair version of tryAcquire.  Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {//1
        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;
}

这里是关键: !hasQueuedPredecessors() ,表示没有等待的前驱节点。也就是没有别的线程先排队。

    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
  • h == t 表示 head 等于 tail,也就是当前队列为空,返回 false。
  • h != t 表示 head 不等于 tail,也就是不为空。进行下一步判断。
  • h != t && (s = h.next) == null 表示 head 的后继节点为空,说明有一个节点需要作为 AQS 第一个节点入列。一样要返回 true。
  • h != t && s!=null && s.thread != Thread.currentThread() 表示 head 的后继节点不为空,而且不等于当前线程。队列第一个元素不是当前线程。

lockInterruptily()

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    
    public final void acquireInterruptibly(int arg) throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg)//尝试获取
            doAcquireInterruptibly(arg);//调用 AQS 可中断方法
    }

tryLock()

   public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

tryLock() 就是非公平锁获取。不会引起线程阻塞。

释放锁

unlock()

    public void unlock() {
        sync.release(1);
    }
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        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;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

使用例子

使用 ReentrantLock 可以实现线程安全的 List。思路是 add/remove/get 方法中 lock/unlock。

public class ReentrantLockList<E> {
    List<E> array = new ArrayList<>();
    volatile ReentrantLock lock = new ReentrantLock();

    public void add(E e) {
        lock.lock();
        try {
            array.add(e);
        } finally {
            lock.unlock();
        }
    }
}

小结

ReentrantLock 使用 AQS 实现可重入的独占锁。state 为 0 表示锁空闲,大于 0 代表所被占用。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

北笙凉宸

暂无简介

0 文章
0 评论
748 人气
更多

推荐作者

内心激荡

文章 0 评论 0

JSmiles

文章 0 评论 0

左秋

文章 0 评论 0

迪街小绵羊

文章 0 评论 0

瞳孔里扚悲伤

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文