读写锁

读写锁

1、JUC 提供两种实现

(1)ReentrantReadWriteLock

(2)StampedLock

2、当读操作远远高于写操作时,使用读写锁,读-读可以并发,提高性能

3、类似于数据库中的 select ... from ... lock in share mode

4、提供一个数据容器类

(1)内部使用读锁保护数据的 read()

(2)内部使用写锁保护数据的 write()

 

ReentrantReadWriteLock

1、返回用于阅读的锁

public ReentrantReadWriteLock.ReadLock readLock()

2、返回用于写入的锁

public ReentrantReadWriteLock.WriteLock writeLock()

3、读锁-读锁:可以并发;读锁-写锁、写锁-写锁:相互阻塞 

4、事项

(1)读锁不支持条件变量,写锁支持条件变量

(2)重入时不支持升级:不允许持有读锁的情况下,去获取写锁,否则导致获取写锁永久等待 

(3)重入时支持降级:允许持有写锁的情况下,去获取读锁

5、写锁状态占 state 的低 16 位,而读锁使用的是 state 的高 16 位

 

读写锁应用:缓存

1、先清理缓存,后更新数据库

(1)更新速度较慢,期间可能发生多次查询

(2)导致数据库、缓存数据不一致

2、先更新数据库,后清空缓存

(1)可能产生数据库、缓存数据不一致

(2)可以保证后续查询数据的正确性

(3)以下情况的出现几率非常小:查询线程 A 查询数据时,恰好缓存数据由于时间到期失效,或是第一次查询

(4)若需要严格的数据一致,则加锁

 

ReentrantReadWriteLock.WriteLock 加写锁

public void lock() {
    sync.acquire(1);
}
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
    //获取当前线程
    Thread current = Thread.currentThread();
    //获取state
    int c = getState();
    //获取写锁(独占锁)计数
    int w = exclusiveCount(c);
    //c != 0,已加锁
    if (c != 0) {
        if (
            //w == 0,说明为读锁
            w == 0 ||
            //w != 0,说明为写锁,判断是否为当前线程的写锁
            current != getExclusiveOwnerThread())
            //加写锁失败
            return false;
        //若为当前线程的写锁,其重入数+1,超过65535,则抛出异常
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        //重入写锁
        setState(c + acquires);
        return true;
    }
    //c == 0,未加锁
    if (
        //是否应该阻塞
        //公平锁:调用hasQueuedPredecessors(),检查 AQS 队列中是否有非空节点, 有则阻塞
        //非公平锁:总返回false
        writerShouldBlock() ||
        //尝试将写锁state从0改为1
        !compareAndSetState(c, c + acquires))
        //加写锁失败,返回false
        return false;
    //加写锁成功,exclusiveOwnerThread设置为当前线程
    setExclusiveOwnerThread(current);
    return true;
}

 

ReentrantReadWriteLock.ReadLock 加读锁

public void lock() {
    sync.acquireShared(1);
}
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

1、tryAcquireShared 返回值

(1)-1:表示失败

(2)0:表示成功,但后继节点不会继续唤醒

(3)正数表示成功,其值表示有几个后继节点需要唤醒

protected final int tryAcquireShared(int unused) {
    //获取当前线程
    Thread current = Thread.currentThread();
    //获取state
    int c = getState();
    if (
        //是否有写锁
        exclusiveCount(c) != 0 &&
        //是否为当前线程的写锁
        getExclusiveOwnerThread() != current)
        //返回-1,表示加读锁失败
        return -1;
    //获取读锁(共享锁)计数
    int r = sharedCount(c);
    if (
        //读锁是否需要阻塞
        !readerShouldBlock() &&
        //读锁数量是否超出65535
        r < MAX_COUNT &&
        //尝试将c+65535,对于高16位,为+1,即读锁计数+1
        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++;
        }
        //读锁+1成功
        return 1;
    }
    //与tryAcquireShared类似,但会不断尝试 for (;;) 获取读锁, 执行过程中无阻塞
    return fullTryAcquireShared(current);
}
private void doAcquireShared(int arg) {
    //创建共享模式节点
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //获取node的前驱节点
            final Node p = node.predecessor();
            //判断p是否为头节点
            if (p == head) {
                //再次尝试获得锁
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    //成功获取读锁
                    //唤醒node所关联的线程,设置node为新的头节点,回收原头节点
                    //r表示可用资源数,允许传播(唤醒AQS中下r个共享节点)
                    //检查下一个是否为共享节点,如果是,将head的state从-1改为0,并唤醒第二个节点的线程
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
private void setHeadAndPropagate(Node node, int propagate) {
    //记录原head,以方便在下面检查
    Node h = head; 
    //设置node为head
    setHead(node);
    //propagate表示有共享资源(例如共享读锁或信号量)
    //原 head waitStatus == Node.SIGNAL 或 Node.PROPAGATE
    //现 head waitStatus == Node.SIGNAL 或 Node.PROPAGATE
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        //获取node的后继节点
        Node s = node.next;
        if (
            //node已经是最后的节点
            s == null ||
            //s是共享节点
            s.isShared())
            //唤醒后续所有共享节点,直到遇见独占节点
            doReleaseShared();
    }
}
private void doReleaseShared() {    
    for (;;) {
        Node h = head;
        //队列还有除head以外的节点
        if (h != null && h != tail) {
            //获取head的waitStatus
            int ws = h.waitStatus;
            //若ws == -1
            if (ws == Node.SIGNAL) {
                //尝试将ws从-1改为0,防止其他线程干扰,即防止多次唤醒第二个节点
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    //修改失败,则继续尝试
                    continue;            // loop to recheck cases
                //修改成功,唤醒head的后继节点
                unparkSuccessor(h);
            }
            //尝试将ws从0改为-3,以解决传播性,
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                //修改失败,继续尝试
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

 

ReentrantReadWriteLock.WriteLock 释放写锁

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        //获取头节点
        Node h = head;
        if (
            //头节点不为null
            h != null &&
            //头节点state不为0
            h.waitStatus != 0)
            //唤醒头节点的后继节点中的线程,后继节点为新的头节点,回收原头节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}
protected final boolean tryRelease(int releases) {
    //当前线程是否持有写锁
    if (!isHeldExclusively())
        //没有持有写锁,则抛出异常
        throw new IllegalMonitorStateException();
    //nextc = state - 1
    int nextc = getState() - releases;
    //写锁可重入计数-1
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        //写锁可重入计数为0,才释放exclusiveOwnerThread的线程
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

 

ReentrantReadWriteLock.ReadLock 释放读锁

public void unlock() {
    sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        //读锁计数为0,才进入if块
        doReleaseShared();
        return true;
    }
    return false;
}
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 (;;) {
        //获取state
        int c = getState();
        //c-65536,对于高16位,等价于-1,即读锁计数-1
        int nextc = c - SHARED_UNIT;
        //尝试修改读锁计数
        if (compareAndSetState(c, nextc))
            //修改成功,则查看读锁计数是否为0
            //读锁为0,则返回true
            //读锁不为0,返回false,但没有变量接收
            return nextc == 0;
    }
}
private void doReleaseShared() {    
    for (;;) {
        Node h = head;
        //队列还有除head以外的节点
        if (h != null && h != tail) {
            //获取head的waitStatus
            int ws = h.waitStatus;
            //若ws == -1
            if (ws == Node.SIGNAL) {
                //尝试将ws从-1改为0,防止其他线程干扰,即防止多次唤醒第二个节点
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    //修改失败,则继续尝试
                    continue;            // loop to recheck cases
                //修改成功,唤醒head的后继节点
                unparkSuccessor(h);
            }
            //尝试将ws从0改为-3,以解决传播性
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                //修改失败,继续尝试
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

 

StampedLock

1、从 JDK 8 加入

2、在使用读锁、写锁时,都必须配合 stamp 使用

(1)加、解读锁

long stamp = lock.readLock();
lock.unlockRead(stamp);

(2)加、解写锁

long stamp = lock.writeLock();
lock.unlockWrite(stamp);

3、乐观读

(1)StampedLock 支持 tryOptimisticRead(),不使用 CAS,优化读性能

(2)读取完毕后,需要做一次 stamp 校验,如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全

long stamp = lock.tryOptimisticRead();
// 验戳
if(!lock.validate(stamp)){
    // 锁升级
}

4、缺点

(1)不支持条件变量

(2)不支持可重入

posted @   半条咸鱼  阅读(70)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示