java 锁之读写锁 ReentrantReadWriterLock 详解

先抛问题,java Lock 有了 ReentrantLock,为什么还需要 ReentrantReadWriteLock ?

  • ReentrantLock 可以解决 Lock 进行同步控制的需求,但在多个线程只需读数据的时候,ReentrantLock 也让线程进行排队控制,影响性能。ReentrantReadWriteLock 应运而生。
  • ReentrantReadWriteLock 维护了一个 读写锁内部类ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock,可以区分线程是读取还是写入操作

ReentrantReadWriteLock 详解

  • ReentrantReadWriteLock 基本用法:读读共享,读写互斥,写写互斥
public class ReadWriteLockTest {
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    ReadLock readLock = lock.readLock();
    WriteLock writeLock = lock.writeLock();

    public void read(){
        readLock.lock();
        try {
            System.out.println("线程"+Thread.currentThread().getName()+"进入。。。");
            Thread.sleep(3000);
            System.out.println("线程"+Thread.currentThread().getName()+"退出。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            readLock.unlock();
        }
    }

    public void write(){
        writeLock.lock();
        try {
            System.out.println("线程"+Thread.currentThread().getName()+"进入。。。");
            Thread.sleep(3000);
            System.out.println("线程"+Thread.currentThread().getName()+"退出。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            writeLock.unlock();
        }
    }


    public static void main(String[] args) {
        final ReadWriteLockTest wr = new ReadWriteLockTest();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                wr.read();
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                wr.read();
            }
        }, "t2");
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                wr.write();
            }
        }, "t3");
        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
                wr.write();
            }
        }, "t4");

        t1.start();
//        t2.start();
        t3.start();
        //t4.start();
    }
}

ReentrantReadWriteLock 源码

  • ReentrantReadWriteLock 也是 AQS 的实现类,即使用 int state = 0 来记录锁状态。

  • 在一个整型变量上维护多种状态,就一定需要“按位切割使用”这个变量,读写锁将变量切分成了两个部分,高16位表示读,低16位表示写,划分方式如下图所示
    在这里插入图片描述

  • 再看ReentrantReadWriteLock 的静态变量

private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
abstract static class Sync extends AbstractQueuedSynchronizer {
	// 定义切割位数
	static final int SHARED_SHIFT   = 16;
    static final int SHARED_UNIT= (1 << SHARED_SHIFT);
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
    /* 共享锁数量 */ 
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    /* 独占锁数量 */ 
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
}
  • readLock.lock(); 底层调用 AQS 的 acquireShared
readLock.lock();  ==> sync.acquireShared(1); 
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
// 老规矩,AQS 定义天上的理念,子类需要落地的实现
protected int tryAcquireShared(int arg) {
    thrownew UnsupportedOperationException();
}
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);
   /* readerShouldBlock 读锁是否应该被阻塞 && 
    *  r < MAX_COUNT 有没有超过最大位数 
	*  compareAndSetState(c, c + SHARED_UNIT) CAS设置 读锁位置(2^16 开始)
	*/
   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);
}

// 尝试共享失败后,doAcquireShared 添加一个Node.SHARED 进同步队列
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 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);
    }
}
  • 再来看 解锁unlock
readLock.unlock();  ==> sync.releaseShared(1); 

public final boolean releaseShared(int arg) {
	// 接触共享 count
    if (tryReleaseShared(arg)) {
    	// 将node 从同步队列移除
        doReleaseShared();
        return true;
    }
    return false;
}
  • 读锁跟 ReentrantLock 一致,都是加入同步队列。可以看从 ReentrantLock 的实现看懂AQS的原理
  • 总结:读写锁就是为了解决读锁共享问题,即建立了一个读读共享标志,选择性的放过读线程的阻塞。

锁降级

  • 锁降级指的是写锁降级成为读锁的过程。指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。
  • 注意,如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。
  • 锁降级的示例代码
public class ReadWriteLockTest1 {
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    ReadLock readLock = lock.readLock();
    WriteLock writeLock = lock.writeLock();
    public static void main(String[] args) {
        final ReadWriteLockTest1 wr = new ReadWriteLockTest1();
        new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            wr.readLock.lock();
            try {
                System.out.println("==============");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                wr.readLock.unlock();
            }
        }).start();
        wr.writeLock.lock();
        try {
            System.out.println("我现在是写锁");
        }finally {
            // 先获取读锁
            wr.readLock.lock();
            try {
                System.out.println("----------睡眠之前--------");
                wr.writeLock.unlock();
                System.out.println("----------锁降级变为读锁之后,就可以共享锁--------");
                Thread.sleep(10000);
                System.out.println("----------睡眠之后--------");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                wr.readLock.unlock();
            }
        }

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值