Java并发编程基础---(11)读写锁---ReadWriteLock

写在前面:

    Mutex(互斥锁)、ReentrantLock(重入锁)本质上都是一种排他锁,在同一时刻只允许一个线程进行访问,而读写锁允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程都会被阻塞。

    ReadWriteLock:

  • 引入:

    在JDK5之前,读写锁的实现,是利用了等待通知机制+synchronized关键字。等待通知机制可以让所有晚于写操作的读操作进入等待状态,而同样都是写操作,则会用一个synchronized来修饰,进行同步。

  • 使用场景:

    在对读操作的要求>写操作的时候,读写锁具有很好的并发性和吞吐量。

  • Java API:

    在java.util.concurrent.lock包下,定义了一个ReadWriteLock接口,但是接口中只有两个方法:

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

我们应用时,是用了他的实现类ReetrantReadWriteLock,他的一些工作状态方法如下:

方法名称描述
getReadLockCount

返回当前读锁被获取的次数。次数不等于读锁的线程数

eg:仅一个线程,连续获取了n次读锁,那么占据读锁的线程是1,但是方法返回值为n

getReadHoldCount返回当前线程获取读锁的次数
isWriteLocked判断写锁是否被获取
getWriteHoldCount返回当前写锁被获取的次数
  • 读写锁状态的设计:

    读写锁与Mutex(互斥锁)、ReentrantLock(重入锁)的同步状态设计有所不同,由于读写锁支持多线程同步读操作,唯一线程写操作,所以仅一个状态位(int 类型)要维护多种状态,就必须将32位的二进制数字,进行按位切割使用。高16位表示读,低16位表示写。

    补充:同步状态是指锁被一个线程重复获取的次数。

    示例:假设当前的状态位为S,那么锁的写状态为S&0X0000FFFF,即将高16位抹去。读状态则S>>>16,即无符号右移16位。当写状态+1时,就等于=S+1,当读状态+1时,就等于=S+(1<<16)=S+0x000100000.

  • 写锁的获取与释放:

在获取写锁的时候,有两种情况

    (1) 当前锁已经获取写锁,这时候,只要增加写状态。

    (2) 如果读锁已经被获取或者该线程不是已经获取写锁的线程,那么当前线程则进入了等待状态。

我们来看获取写锁的tryAcquire方法:

        protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

    读写锁是对读锁,写锁都可见的锁,所以说,当读锁被线程获取的时候,写锁未被获取时,写锁也不能被当前线程所获取,因为如果这是获取了写锁,则获取读锁的线程读到的数据不是最新的数据。

  • 读锁的获取与释放:

读锁的获取也有两种情况:

    (1) 当前没有线程占有写锁时,读锁会直接获取。

    (2) 如果当前线程已经占据读锁,请求重新获取读锁,或增加读状态。

    (3) 如果当前写锁已经被其他线程获取,那么当前线程则进入等待状态。

        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)) {
                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;
            }
            return fullTryAcquireShared(current);
        }

  • 锁降级:

    锁降级到过程是:

    (1) 当前线程持有写锁

    (2)当前线程再获取到读锁

    (3)释放掉原来持有的写锁

这一过程则成为锁降级,另外,ReetrantReadWriteLock不支持锁升级(把持读锁>>获取写锁>>释放读锁)。

注:为什么必须有第二步获取读锁才能释放写锁呢?

    因为,假设当前线程A持有写锁,然后直接释放掉写锁。再去获取读锁,因为写锁已经被释放,那么其他线程B就会获取到写锁,对数据进行更新,此时当前线程修改的数据,可能对线程A是不可见的,所以,这种操作不锁降级。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值