Java之可重入锁--ReentrantLock


重入性:表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。

重入性的实现原理

判断当前线程能否获得锁为例,核心方法为
nonfairTryAcquire():

 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) 
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);//更新锁的状态
                return true;
            }
            return false;
        }

释放锁的核心方法是tryRelease():

 protected final boolean tryRelease(int releases) {
            // 同步状态-1
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();//非持有锁线程调用tryRelease()方法抛出此异常
            boolean free = false;
            if (c == 0) {
                // 只有当同步状态为0时,锁成功释放,返回false
                free = true;
                setExclusiveOwnerThread(null);
            }
            // 锁未被完全释放,返回fal
            setState(c);
            return free;
        }

公平锁与非公平锁

公平锁:锁的获取顺序符合请求上的绝对时间顺序,满足FIFO(先进先出)。
ReentrantLock的构造方法无参时是构造非公平锁,源
码为:

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

另外还提供了另外一种方式,可传入一个boolean值,true时为公平锁,false时为非公平锁,源码为:

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

如下图,观察公平锁与非公平锁的区别,公平锁获取同步状态之前先判断当前结点是否有前驱结点,如果有就获取失败,实现了先进先出的规则。
在这里插入图片描述

公平锁和非公平锁的比较

1.公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。
2. 公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,效率低。而非公平锁会降低一定的上下文切换,降低性能开销效率高。
3. ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。

深入理解读写锁ReentrantReadWriteLock

读写锁:允许同一时刻被多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程都会被阻塞。
读锁==无锁?

  肯定不等于。因为在写线程访问的时候,所有的读线程不能再访问,被阻塞。

写锁

写锁的获取

然写锁是独占式锁,而实现写锁的同步语义是通过重写AQS中的tryAcquire方法实现的。源码为:

protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            //判断当前同步状态
            int c = getState();
            //获取写锁获取的次数
            int w = exclusiveCount(c);
            if (c != 0) {
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                setState(c + acquires);
                return true;
            }
            //写锁未被任何线程获取,当前线程获取写锁
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
                //获取写锁成功
            setExclusiveOwnerThread(current);
            return true;
        }

exclusiveCount©方法,我们怎么直到他获取的是读写还是写锁呢?看该方法源码:

在这里插入图片描述
其 中 EXCLUSIVE_MASK 为 : static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; EXCLUSIVE_MASK为1左移16位然后减1,即为0x0000FFFF。而exclusiveCount方法是将同步状态(state为int类型)与0x0000FFFF相与,即取同步状态的低16位。那么低16位代表什么呢?根据exclusiveCount方法的注释为独占式获取的次数即写锁被获取的次数,现在就可以得出来一个结论:同步状态的低16位用来表示写锁的获取次数。
再看上图中的sharedCount()方法。该方法是获取读锁被获取的次数,是将同步状态(int c)右移16次,即取同步状态的高16位,现在我们可以得出另外一个结论同步状态的高16位用来表示读锁被获取的次数。

扫描二维码关注公众号,回复: 9807263 查看本文章

写锁的释放

protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
                // 同步状态减去写状态
            int nextc = getState() - releases;
            // 当前写状态是否为0,为0则释放
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                setExclusiveOwnerThread(null);
                // 不为0则更新同步
            setState(nextc);
            return free;
        }

读锁

读锁的获取

读锁不是独占式锁,即同一时刻该锁可以被多个读线程获取也就是一种共享式锁。

     protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
            // 如果写锁已经被获取并且获取写锁的线程不是当前线程
            // 线程获取读锁失败并返回
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                // 当前线程获取
                compareAndSetState(c, c + SHARED_UNIT)) {
                // 新增关于读锁的一些功能,比如getReadHoldCount()方法返回
                // 当前获取读锁的
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            // CAS失败或者已经获取读锁的线程再次重入
            return fullTryAcquireShared(current);
        }

读锁的释放

 protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    // Releasing the read lock has no effect on readers,
                    // but it may allow waiting writers to proceed if
                    // both read and write locks are now free.
                    return nextc == 0;
            }
        }

锁降级:遵循按照获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁,不支持锁升级

发布了87 篇原创文章 · 获赞 73 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/HL_HLHL/article/details/84761452