Java 多线程并发 ReentrantReadWriteLock详情

目录
  • 前言
  • ReadWriteLock
  • ReentrantReadWriteLock 源码分析
    • 类关系
    • Sync
    • HoldCounter
    • ThreadLocalHoldCounter
    • 属性
    • 构造方法
    • 核心方法
    • 锁的计数方法
    • 读写锁阻塞检查方法
    • 公平策略实现 FairSync 和非公平策略实现 NonfairSync
    • NonfairSync 非公平策略
    • FairSync 公平策略
    • Release 和 Acquire 方法组
    • ReadLock
    • WriteLock
  • 读写锁降级
  • 总结

前言

ReentrantReadWriteLock ,可重入读写锁。实际使用场景中,我们需要处理的操作本质上是读与写。而对这两种操作进行同步操作的难度也是不一样的。

一般情况下,读操作不会造成同步安全问题,因为只是读取数据而不去修改的情况下相当于数据是不可变的,不可变本质上是绝对的线程安全,无需进行任何确保线程安全的操作。

而如果在一系列操作中包含了写操作,那么就需要考虑线程安全了。在 JMM 中,写操作本质上是将主内存中的数据复制到线程的工作内存,然后进行更新,最后同步到主内存。如果此时有其他线程执行读操作,可能会读取到更新前到旧数据,就会造成数据不一致问题。

JMM 中定义的对写操作的执行流程中,要先去主内存读取数据,也就是说,一个写操作前一定包含了一个读操作,再算上其他的读操作场景,可以得出结论,在实际的使用场景中,读操作一定是多于写操作的。

按照上面的说法,好像读操作我们不需要进行线程安全处理,因为它本身就是线程安全的,那么为什么会有读写锁,尤其是读锁这种东西存在呢?

试想一个场景,多个线程读取一个共享资源,其中某个或某些线程在不确定的时间点会进行写操作,那么所有线程的读取到的数据是安全的吗?答案是不安全,因为写操作写入主内存不及时的话,后续其他线程的读操作读取到的数据就是主内存更新前的旧数据,就会导致脏数据问题。也就是说,写操作需要保证线程安全,并且是独占锁资源的,不能再写操作执行时,存在其他线程去执行读操作。那么就需要读锁与写锁配合处理同步逻辑。

常规的保证线程安全的方法就是普通的互斥锁,互斥锁会被一个线程持有,对其他线程造成阻塞。如果对一段有读操作也有写操作的代码使用互斥锁的话,对于争用这个共享数据的所有线程来说,只有一个拥有锁的线程可以正常运行,其他线程的逻辑即使是都是读操作。其他线程会阻塞等待锁资源。

读写锁的优势就是,在上面这种情况下,确保写操作的互斥性,并在没有写操作的场景下,读操作可以让多个线程同时获取锁资源。

ReadWriteLock

ReentrantReadWriteLock 是基于 AbstractQueuedSynchronizer 并实现了 ReadWriteLock 接口实现的一个锁机制。ReadWriteLock 定义了读写锁的特性:

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     */
    Lock readLock();
    /**
     * Returns the lock used for writing.
     */
    Lock writeLock();
}

ReadWriteLock 中定义了获取两种锁的方式,一个用于获取读锁、一个用于获取写锁。只要没有持有写锁的线程在执行,读锁可以同时被多个尝试读操作的线程持有,而写锁是排他锁。

与互斥锁相比,读写锁在访问共享数据时允许更高级的并发特性,即每次只有一个线程可以执行写操作,并且在没有写操作时其他线程可以并发读取共享数据。从读操作的效率来看,如果是互斥锁每次只能一个线程执行读写操作,而读写锁可以多个线程读,写操作时才互斥,所以读写锁的执行效率更高。

ReentrantReadWriteLock 源码分析

前面的内容介绍了读写锁的含义和优势,接下来分析 Java 并发包中对它的实现 ReentrantReadWriteLock 。

类关系

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    abstract static class Sync extends AbstractQueuedSynchronizer {
      	static final class HoldCounter
        static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter>
    }
    static final class NonfairSync extends Sync
    static final class FairSync extends Sync
    public static class ReadLock implements Lock, java.io.Serializable
    public static class WriteLock implements Lock, java.io.Serializable
}

ReentrantReadWriteLock 实现了读写锁接口 ReadWriteLock 和序列化接口 Serializable 。

它有一个抽象静态内部类 Sync ,Sync 是 AQS 的抽象子类,Sync 有两个静态实现 NonfairSync 和 FairSync ,这部分是锁逻辑的核心内容;Sync 还有两个内部数据结构类 HoldCounter 和 ThreadLocalHoldCounter 。

ReadLock 和 WriteLock 分别对应了读锁和写锁,它们都实现了 Lock 接口和序列号接口 Serializable 。它们是 ReentrantReadWriteLock 中对不同操作的锁类型的实现,使用了装饰模式,本质上还是通过 Sync 的能力实现的。

Sync

核心逻辑是来自于 Sync 及其两个实现,Sync 继承自 AbstractQueuedSynchronizer ,自身有两个内部类 HoldCounter 和 ThreadLocalHoldCounter 。

HoldCounter

static final class HoldCounter {
    int count;          // initially 0
    // Use id, not reference, to avoid garbage retention
    final long tid = LockSupport.getThreadId(Thread.currentThread());
}

HoldCounter 是一个计数器,count 用来记录当前线程拥有读锁的数量,即读锁的重入次数;tid 用来记录当前线程唯一 ID 。

Sync 有一个 cachedHoldCounter 属性,用来做缓存效果,避免每次都通过 ThreadLocal 去读取数据。

ThreadLocalHoldCounter

static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}

ThreadLocalHoldCounter 重写了 ThreadLocal 的 initialValue() ,在 ThreadLocal 没有进行过 set 数据的情况下,默认读取到的值都来自于这个方法,也就是配合 ThreadLocal 使用,默认值返回一个新的 HoldCounter 实例。

在 Sync 中,有一个属性 readHolds ,它的类型是 ThreadLocalHoldCounter ,用来做当前线程读锁重入计数器的 ThreadLocal 包装,便于线程读取自己的读锁重入计数器。

属性

Sync 中定义的属性包括:

abstract static class Sync extends AbstractQueuedSynchronizer {
		// 高16位为读锁,低16位为写锁
    static final int SHARED_SHIFT   = 16;
    // 读锁单位
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);  // 1 * 2^16 = 65536
    // 读锁最大数量
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;  // 2^16 - 1
    // 写锁最大数量
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;   // 2^16 - 1 独占标记
    // 当前线程读锁重入次数。当持有读锁的线程数量下降到0时删除。
    private transient ThreadLocalHoldCounter readHolds;
    // 缓存对象,避免每次都去从 ThreadLocal 查找。
    private transient HoldCounter cachedHoldCounter;
		// 第一个获取读锁线程
    private transient Thread firstReader;
    // 第一个读锁线程重入读锁的计数
    private transient int firstReaderHoldCount;
    // ...
}

构造方法

Sync() {
		readHolds = new ThreadLocalHoldCounter();
    setState(getState()); // ensures visibility of readHolds
}

Sync 初始化方法创建了 ThreadLocalHoldCounter 并重新设置了 State ,为什么要重新设置呢?因为这里要读取当前线程最新的同步状态并重新设置,获取实时的同步状态。

核心方法

Sync 的关键方法包括:

abstract static class Sync extends AbstractQueuedSynchronizer {
  	// 并发计数
    static int sharedCount(int c)
    static int exclusiveCount(int c)
		// 阻塞检查
    abstract boolean readerShouldBlock();
    abstract boolean writerShouldBlock();
		// 获取和释放写锁
    @ReservedStackAccess
    protected final boolean tryRelease(int releases)
    @ReservedStackAccess
    protected final boolean tryAcquire(int acquires)
		// 获取和释放读锁
    @ReservedStackAccess
    protected final boolean tryReleaseShared(int unused)
    @ReservedStackAccess
    protected final int tryAcquireShared(int unused)
    final int fullTryAcquireShared(Thread current)
		// 尝试加读写锁
    @ReservedStackAccess
    final boolean tryWriteLock()
    @ReservedStackAccess
    final boolean tryReadLock()

    // ...
}

锁的计数方法

首先是两个静态方法 sharedCount(int c) 和 exclusiveCount(int c) :

/** 表示共享持有的数量。 */
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; } // 无符号右移,高位补 0
/** 表示独占持有的数量。 */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

参数 c 是 AQS 中的 state,根据 state 进行位运算。这两个方法可以根据锁自身的状态解析出持有读写锁的数量。

  • sharedCount ,表示占有读锁的线程数量。直接将 AQS 中的 state 右移 16 位,高位补 0,就可以得到读锁的线程数量,因为 state 的高十六位表示读锁,对应的低十六位表示写锁数量。
  • exclusiveCount,表示占有写锁的线程数量。直接将 AQS 的 state 和 (2^16 - 1) 做与运算,其等效于将 state 模上 2^16 。写锁数量由 state 的低十六位表示。

读写锁阻塞检查方法

第二组方法是 readerShouldBlock 和 writerShouldBlock ,用来检查当前的读锁/写锁是否会造成当前线程阻塞。

// 获取和释放对公平锁和非公平锁使用相同的代码,不同点在于但在队列非空时是否/如何允许碰撞。

// 如果当前线程在尝试获取读锁时,并且在其他符合条件的线程也在尝试获取读锁,由于策略其他等待线程占用了读锁,当前线程应该阻塞,则返回true。
abstract boolean readerShouldBlock();

// 如果当前线程在尝试获取写锁时,并且在其他符合条件的线程也在尝试获取写锁,由于策略其他等待线程占用了写锁,当前线程应该阻塞,则返回true。
abstract boolean writerShouldBlock();

这两个方法的实现在 Sync 的子类中 -- 公平策略实现 FairSync 和非公平策略实现 NonfairSync。

公平策略实现 FairSync 和非公平策略实现 NonfairSync

    // 非公平策略
		static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // 正在持有写锁的线程永不阻塞
        }
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
    }
		// 公平策略
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;

        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }

公平锁策略和非公平锁策略的实现,本质上的不同是这两个方法的实现。

NonfairSync 非公平策略

NonfairSync 中,执行写操作的线程是否应该进入阻塞状态的判断,直接是 false ,这是因为非公平策略下,如果当前自身已经拥有了写锁,直接重入,以独占的方式继续运行(所以是不公平的)。

执行读操作的线程是否会阻塞,是通过 apparentlyFirstQueuedIsExclusive() 判断的,这个方法是 AQS 中的方法:

    final boolean apparentlyFirstQueuedIsExclusive() {
        Node h = head, s = head.next;
        return h != null && s != null && !(s instanceof SharedNode) && s.waiter != null;
    }

这个方法的作用是,CLH 队列中的头节点和它的的 next 都存在的情况下,如果 next 节点不是 SharedNode ,且它的关联线程不为空的情况(即下一个锁不是共享锁,共享锁在读写锁里就是读锁)的情况,会导致当前执行读操作的线程进入阻塞状态,确保写操作的互斥特性。

FairSync 公平策略

FairSync 中,读写执行线程是否应该进入阻塞状态都是根据 hasQueuedPredecessors() 方法判断的:

    public final boolean hasQueuedPredecessors() {
        Thread first = null; Node h = head, s = h.next;
        if (h != null && (s == null || (first = s.waiter) == null || s.prev == null))
            first = getFirstQueuedThread(); // retry via getFirstQueuedThread
        return first != null && first != Thread.currentThread();
    }

    public final Thread getFirstQueuedThread() {
        Thread first = null, w; Node h, s;
        if ((h = head) != null && ((s = h.next) == null || (first = s.waiter) == null || s.prev == null)) {
            // traverse from tail on stale reads
            for (Node p = tail, q; p != null && (q = p.prev) != null; p = q)
                if ((w = p.waiter) != null)
                    first = w;
        }
        return first;
    }

hasQueuedPredecessors() 对 head 节点和它的 next 节点进行空检查,并检查下一个节点的执行线程和 prev 指针是否有值,满足条件的情况下通过 getFirstQueuedThread() 方法获取到队列中第一个节点关联的线程。最终返回的结过是检查这个线程不等于当前线程。

如果存在等待队列第一个等待执行的线程,那么就优先执行这个线程。也就是说,不管当前线程是拥有读锁还是写锁,都优先执行等待队列第一个未执行节点,这里就能体现出公平,即优先执行等待队列中头一个等待的节点所关联的线程。

Release 和 Acquire 方法组

这一组方法是整个 Sync 的核心逻辑,也是加解锁核心逻辑。

tryRelease

@ReservedStackAccess
protected final boolean tryRelease(int releases) {
		if (!isHeldExclusively()) // 不是独占持有锁的情况,直接抛出异常。
				throw new IllegalMonitorStateException();
		int nextc = getState() - releases; // AQS 当前锁状态 - releases = 新的锁状态
		boolean free = exclusiveCount(nextc) == 0; // 根据新的锁状态获取到独占写锁的数量 == 0
		if (free)
				setExclusiveOwnerThread(null); // 持有写锁的线程数为0,更新当前独占线程引用
		setState(nextc); 	// 无论是不是解锁了,都要更新锁状态
		return free;			// 最后返回锁是否已经可用了
}

tryRelease(int releases) 用来尝试释放写锁。

它的逻辑如下图:

tryAcquire

@ReservedStackAccess
protected final boolean tryAcquire(int acquires) {
            /*
             * 工作流程:
             * 1. 如果写锁计数非零或所有者是不同的线程,则失败。
             * 2. 如果写锁计数超过最大数量,失败(这只发生在计数非 0 的情况)。
             * 3. 否则,如果这个线程是可重入的获取方式或者队列策略允许的话,它就有资格获得锁。
             *    如果是,更新状态并设置 owner。
             */
		Thread current = Thread.currentThread(); // 当前线程
		int c = getState();											 // 当前锁状态
		int w = exclusiveCount(c);							 // 计算拥有写锁的线程数量
		if (c != 0) { // 0 是锁可用状态,当前状态表面锁状态为被持有。
				if (w == 0 || current != getExclusiveOwnerThread()) // 对应 【1】 的情况,写线程数量为0或者当前线程没有占有独占资源
						return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT) // 对应【2】的情况, 判断是否超过最高写线程数量
            throw new Error("Maximum lock count exceeded");
        // 重入获取写锁
        setState(c + acquires);
        return true;
		}
		if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) // 是否应该阻塞或更新状态是否成功,失败直接 return false;
				return false;
		setExclusiveOwnerThread(current); // 设置当前为持有锁的线程。
		return true;
}

此函数用于获取写锁,首先会获取 state ,判断 state 是否为0。

若为0,表示此时没有读锁线程,再判断写线程是否应该被阻塞,而在非公平策略下总是不会被阻塞,在公平策略下会进行判断(判断同步队列中是否有等待时间更长的线程,若存在,则需要被阻塞,否则,无需阻塞),之后在设置状态state,然后返回true。若state不为0,则表示此时存在读锁或写锁线程,若写锁线程数量为0或者当前线程为独占锁线程,则返回false,表示不成功,否则,判断写锁线程的重入次数是否大于了最大值,若是,则抛出异常,否则,设置状态state,返回true,表示成功。

其函数流程图如下:

tryReleaseShared:

        @ReservedStackAccess
        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 != LockSupport.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;
            }
        }

tryAcquireShared :

        @ReservedStackAccess
        protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) // 当独占线程不是当前线程
                return -1;
            int r = sharedCount(c); // 共享读锁的线程数量
          	// 检查读线程不应该阻塞 and 持有读锁的线程数量小于 MAX_COUNT and 更新锁状态成功
            if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {  // 第一个尝试获取读锁的线程
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {  // 第一个线程重入
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                  	// 无缓存 or 当前线程不是计数器所在线程
                    if (rh == null || rh.tid != LockSupport.getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get(); // 从 ThreadLocal 中读取
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++; // 当前线程获取读锁次数 + 1
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

最后执行到了 fullTryAcquireShared :

        final int fullTryAcquireShared(Thread current) {
            /*
             * 这段代码与 tryAcquireShared 中的部分代码是冗余的,但总体上更简单,因为它不会使
             * tryAcquireShared 在重试和懒加载读锁计数之间的交互复杂化。
             */
            HoldCounter rh = null;
            for (;;) { // 死循环,不断尝试
                int c = getState();
                if (exclusiveCount(c) != 0) { // 独占检查是否是当前线程
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                // 否则我们持有独占锁;这里的阻塞将导致死锁。
                } else if (readerShouldBlock()) {
                    // 确保我们不是重入式地获取读锁
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {
                      	// 不是重入的情况下,更新 HoldCounter
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != LockSupport.getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
              	// 共享读锁 == 最大数量,抛出异常
                if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded");
              	// 是否能够设置成功
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    if (sharedCount(c) == 0) { // 第一个线程
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) { // 重入
                        firstReaderHoldCount++;
                    } else { // 其他情况
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != LockSupport.getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }

这个方法的整体逻辑与 tryAcquireShared 基本相同。

ReadLock

public static class ReadLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -5992448646407690164L;
    private final Sync sync;
    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
    public void lock() {
        sync.acquireShared(1);
    }
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    public boolean tryLock() {
        return sync.tryReadLock();
    }
    public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
    public void unlock() {
        sync.releaseShared(1);
    }
    public Condition newCondition() {
        throw new UnsupportedOperationException();
    }
    // ...
}

ReadLock 实现了 Lock 接口,代理调用到逻辑都是 Sync 中 Shared 组的核心方法。ReadLock 可以通过 readLock(): ReadLock 方法获取到。

还有一点值得注意,newCondition() 方法直接抛出了异常,这是因为读锁是一种共享锁,不会导致互斥,所以也就不支持使用 Condition 控制阻塞与唤醒。

WriteLock

public static class WriteLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -4992448646407690164L;
    private final Sync sync;
    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
    public void lock() {
        sync.acquire(1);
    }
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    public boolean tryLock() {
        return sync.tryWriteLock();
    }
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
    public void unlock() {
        sync.release(1);
    }
    public Condition newCondition() {
        return sync.newCondition();
    }
    public String toString() {
        Thread o = sync.getOwner();
        return super.toString() + ((o == null) ? "[Unlocked]" : "[Locked by thread " + o.getName() + "]");
    }
   public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }
    public int getHoldCount() {
        return sync.getWriteHoldCount();
    }
}

写锁本质上也是代理 Sync 中的核心方法。

读写锁降级

锁降级指的是写锁降级为读锁,如果当前线程拥有写锁,将其释放然后再获取读锁,这种操作过程不是锁降级。锁降级是指把线程当前持有写锁,再去获取读锁,随后释放写锁,这个流程称为锁降级。

public void processData() {
    readLock.lock();
    if (!update) {
        // 必须先释放读锁
        readLock.unlock();
        // 锁降级从写锁获取到开始
        writeLock.lock();
        try {
            if (!update) {
                // 准备数据的流程(略)
                update = true;
            }
            readLock.lock();
        } finally {
            writeLock.unlock();
        }
        // 锁降级完成,写锁降级为读锁
    }
    try {
        // 使用数据的流程(略)
    } finally {
        readLock.unlock();
    }
}

锁降级可以保证数据的可见性,如果再持有写锁的情况下,不先去获取读锁,直接释放写锁,再尝试获取读锁,这一系列操作中会有短暂的无锁状态,此时如果有其他线程获取了写锁并修改数据,那么当前线程就无法感知到数据更新,如果当前线程先获取了读锁,那么其他线程就会阻塞,直到当前线程释放读锁后才能获取写锁进行更新。

读写锁 ReentrantReadWriteLock 不支持锁升级,目的是保证数据的可见性,如果读锁已被多个线程获取,其中任意线程成功获取了写锁,并更新了数据,那么这个更新对其他线程是不可见的,容易造成数据不一致问题。

总结

  • ReentrantReadWriteLock 底层加解锁原理是 AQS
  • ReentrantReadWriteLock 分为 ReadLock 和 WriteLock 两种锁,ReadLock 是共享锁,WriteLock 是互斥锁。
  • ReentrantReadWriteLock 的写锁可重入是根据 AQS 中的 state 计数的;读锁的可重入是 Sync 中的 HoldCounter 来记录的。
  • 公平策略和非公平策略都需要对读锁和写锁分别实现一个判断逻辑。
  • 核心实现在 Sync 方法中。

到此这篇关于Java 多线程并发 ReentrantReadWriteLock详情的文章就介绍到这了,更多相关Java ReentrantReadWriteLock内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java concurrency之共享锁和ReentrantReadWriteLock_动力节点Java学院整理

    ReadWriteLock 和 ReentrantReadWriteLock介绍 ReadWriteLock,顾名思义,是读写锁.它维护了一对相关的锁 - - "读取锁"和"写入锁",一个用于读取操作,另一个用于写入操作. "读取锁"用于只读操作,它是"共享锁",能同时被多个线程获取. "写入锁"用于写入操作,它是"独占锁",写入锁只能被一个线程锁获取. 注意:不能同时存在读取锁和写入锁

  • Java多线程之ReentrantReadWriteLock源码解析

    一.介绍 1.1 ReentrantReadWriteLock ReentrantReadWriteLock 是一个读写锁,允许多个读或者一个写线程在执行. 内部的 Sync 继承自 AQS,这个 Sync 包含一个共享读锁 ReadLock 和一个独占写锁 WriteLock. 该锁可以设置公平和非公平,默认非公平. 一个持有写锁的线程可以获取读锁.如果该线程先持有写锁,再持有读锁并释放写锁,称为锁降级. WriteLock支持Condition并且与ReentrantLock语义一致,而Re

  • Java多线程 ReentrantReadWriteLock原理及实例详解

    读写锁ReentrantReadWriteLock概述 读写锁ReentrantReadWriteLock,使用它比ReentrantLock效率更高. 读写锁表示两个锁,一个是读操作相关的锁,称为共享锁:另一个是写操作相关的锁,称为排他锁. 1.读和读之间不互斥,因为读操作不会有线程安全问题 2.写和写之间互斥,避免一个写操作影响另外一个写操作,引发线程安全问题 3.读和写之间互斥,避免读操作的时候写操作修改了内容,引发线程安全问题 多个Thread可以同时进行读取操作,但是同一时刻只允许一个

  • Java多线程读写锁ReentrantReadWriteLock类详解

    目录 ReentrantReadWriteLock 读读共享 写写互斥 读写互斥 源码分析 写锁的获取与释放 读锁的获取与释放 参考文献 真实的多线程业务开发中,最常用到的逻辑就是数据的读写,ReentrantLock虽然具有完全互斥排他的效果(即同一时间只有一个线程正在执行lock后面的任务),这样做虽然保证了实例变量的线程安全性,但效率却是非常低下的.所以在JDK中提供了一种读写锁ReentrantReadWriteLock类,使用它可以加快运行效率. 读写锁表示两个锁,一个是读操作相关的锁

  • Java 多线程并发 ReentrantReadWriteLock详情

    目录 前言 ReadWriteLock ReentrantReadWriteLock 源码分析 类关系 Sync HoldCounter ThreadLocalHoldCounter 属性 构造方法 核心方法 锁的计数方法 读写锁阻塞检查方法 公平策略实现 FairSync 和非公平策略实现 NonfairSync NonfairSync 非公平策略 FairSync 公平策略 Release 和 Acquire 方法组 ReadLock WriteLock 读写锁降级 总结 前言 Reentr

  • Java 多线程并发AbstractQueuedSynchronizer详情

    目录 AbstractQueuedSynchronizer 核心思想 为什么需要 AQS 用法 用法示例 AQS 底层原理 父类 AbstractOwnableSynchronizer CLH 队列 Condition 用于等待的方法 用于唤醒的方法 ConditionObject Signalling methods Waiting methods enableWait canReacquire unlinkCancelledWaiters 对外提供的等待方法 awaitUninterrupt

  • Java 多线程并发编程_动力节点Java学院整理

    一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线程:表示程序的执行流程,是CPU调度执行的基本单位:线程有自己的程序计数器.寄存器.堆栈和帧.同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源. 2.Java标准库提供了进程和线程相关的API,进程主要包括表示进程的jav

  • Java多线程并发编程(互斥锁Reentrant Lock)

    Java 中的锁通常分为两种: 通过关键字 synchronized 获取的锁,我们称为同步锁,上一篇有介绍到:Java 多线程并发编程 Synchronized 关键字. java.util.concurrent(JUC)包里的锁,如通过继承接口 Lock 而实现的 ReentrantLock(互斥锁),继承 ReadWriteLock 实现的 ReentrantReadWriteLock(读写锁). 本篇主要介绍 ReentrantLock(互斥锁). ReentrantLock(互斥锁)

  • Java多线程并发开发之DelayQueue使用示例

    在学习Java 多线程并发开发过程中,了解到DelayQueue类的主要作用:是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走.这种队列是有序的,即队头对象的延迟到期时间最长.注意:不能将null元素放置到这种队列中. Delayed,一种混合风格的接口,用来标记那些应该在给定延迟时间之后执行的对象.此接口的实现必须定义一个 compareTo 方法,该方法提供与此接口的 getDelay 方法一致的排序. 在网上看到了一些

  • Java多线程并发编程 Volatile关键字

    volatile 关键字是一个神秘的关键字,也许在 J2EE 上的 JAVA 程序员会了解多一点,但在 Android 上的 JAVA 程序员大多不了解这个关键字.只要稍了解不当就好容易导致一些并发上的错误发生,例如好多人把 volatile 理解成变量的锁.(并不是) volatile 的特性: 具备可见性 保证不同线程对被 volatile 修饰的变量的可见性. 有一被 volatile 修饰的变量 i,在一个线程中修改了此变量 i,对于其他线程来说 i 的修改是立即可见的. 如: vola

  • Java多线程并发编程和锁原理解析

    这篇文章主要介绍了Java多线程并发编程和锁原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.前言 最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题. 在运用了 限流/加锁等方案后,问题得到解决. 加锁方案见下文. 二.乐观锁 & 悲观锁 1.乐观锁 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁

  • Java多线程并发与并行和线程与进程案例

    目录 一.并发与并行 二.线程与进程 三.创建线程类 前言: 程序在没有跳转语句的前提下,都是由上至下依次执行,那现在想要设计一个程序,边打游戏边听歌,怎么设计? 要解决上述问题,咱们得使用多进程或者多线程来解决. 一.并发与并行 并发:指两个或多个事件在同一个时间段内发生. 并行:指两个或多个事件在同一时刻发生(同时发生). 在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过

  • Java 多线程并发ReentrantLock

    目录 背景 ReentrantLock 可重入特性 公平锁设置参数 源码分析 Lock 接口 加锁操作 内部类 Sync tryLock initialTryLock lock lockInterruptibly tryLockNanos tryRelease newCondition NonfairSync 非公平锁 FairSync 构造函数 核心属性和方法 总结 背景 在 Java 中实现线程安全的传统方式是 synchronized 关键字,虽然它提供了一定的同步能力,但它在使用上是严格

  • Java多线程并发synchronized 关键字

    目录 基础 修饰普通方法 修饰静态方法 Synchronized 加锁原理 monitorenter monitorexit synchronized 修饰静态方法 优点.缺点及优化 其他说明 基础 Java 在虚拟机层面提供了 synchronized 关键字供开发者快速实现互斥同步的重量级锁来保障线程安全. synchronized 关键字可用于两种场景: 修饰方法. 持有一个对象,并执行一个代码块. 而根据加锁的对象不同,又分为两种情况: 对象锁 类对象锁 以下代码示例是 synchron

随机推荐