JAVA可重入锁ReentrantLock

今天有点时间阅读一下java的重入锁,本应该先将AbstractQueuedSynchronizer说明白再写这篇文章,无所谓了那就随缘先将此讲清楚吧~
打开 ReentrantLock锁的源码我们首先看到的是

private final Sync 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.
     */
    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.
         */
        abstract void lock();

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

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

        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

        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 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
        }
    }

我们都知道java很多的同步都是通过对对象进行加锁,或者说通过调运计算机cas原子指令进行控制的,重入锁也不例外,是通过两个内部类完成的一个是 NonfairSync(不公平锁),另外一个FairSync(公平锁)都是Sync的子类
Sync之所以继承AbstractQueuedSynchronizer 主要是希望利用里面已经封装好的,一些cas操作,还有排队的机制,具体细节在AbstractQueuedSynchronizer 解析中再写。大家有一个概念就好
我们具体分析一下Sync里面的三个方法

abstract void lock();

这个方法是会在公平锁与非公平锁实现中实现不同逻辑以完成各自的公平与非公平

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;

获得当前状态值,这个地方先mark一下这里这个变量是volatile的,为了做到多个线程间的状态值透明(重入锁的实现其实就是看这个state的值,当state是0的时候就意味着当前没有线程持有锁),如果线程冲入一次那么这个state的值就加1,这也是为什么重入锁的上锁跟释放锁需要配对出现的原因。

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

tryRelease 这个方法是通过修改state状态进行锁的释放一会在下面的代码中会发现会调用tryRelease(1)想进行状态值计算,将状态值减1,判断当前线程是不是正在执行线程,如果不是就抛出异常,判断如果C等于1也就是锁完全释放了,则将线程只有对象改为null,然后刷新state变量,这个方法会帮忙返回目前锁是否处于空闲状态。

两种锁

不公平锁上来先去抢一下,如果不成功再进行排队,而公平锁先进行排队尝试这是两个锁本质区别,在尝试加锁时候公平锁会多判断一次队列中是否有内容,有内容则直接去排队

不公平锁NonfairSync

 static final class NonfairSync extends 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);
        }
    }

不公平锁竞争lock方法,显示尝试原子性操作,如果成功则将执行线程设置为自己,否则就会调用acquire方法,这个acpuire是继承自AQS这个类的

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

第一个条件是又调回了下面的代码

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

总结思想就是
我上来先加一次锁,加上了我就设置执行线程为我自己,否则我就去取调用acquire方法,在调用的时候会再检查一下,如果锁空闲我就设置成我自己,否则就把当前线程封装成node进行排队。

公平锁

static final class FairSync extends 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.
         */
        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;
        }
    }

我们看到了公平锁加锁是先调用acquire方法,最后会回来到tryAcquire方法,多了一个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());
    }

大意就是判断这会这个node链表是不是空的,为了防止重排队多了个判断下个要执行的不是自己,最后一行代码。
最后我们简单看一下释放锁的方法

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

这里面会去通过tryRelease去修改state状态,知道修改为0的时候表示当前锁释放然后条件为ture,开始从队列里面找到头,喊起来执行。
其他方法感觉不需要过多解释了,如果理解有不对地方欢迎拍砖。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容