ReadWriteLock

   package java.util.concurrent.locks;
   ReadWriteLock接口
   它保证:
              只允许一个线程写入(其他线程既不能写入也不能读取);
              没有写入时,多个线程允许同时读(提高性能)。
              读的时候不能写   也就是说 
                 读-写互斥,写-写互斥,读读不互斥
其实现类ReentrantReadWriteLock
public class Counter {
    private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
    private final Lock rlock = rwlock.readLock();  //获取读锁
    private final Lock wlock = rwlock.writeLock();//获取写锁
    private int[] counts = new int[10];
    public void inc(int index) {
        wlock.lock(); // 加写锁
        try {
            counts[index] += 1;
        } finally {
            wlock.unlock(); // 释放写锁
        }
    }

    public int[] get() {
        rlock.lock(); // 加读锁
        try {
            return Arrays.copyOf(counts, counts.length);
        } finally {
            rlock.unlock(); // 释放读锁
        }
    }
}


把读写操作分别用读锁和写锁来加锁,在读取时,多个线程可以同时获得读锁,
 适用于多读少写的场景
 
获取锁顺序  
非公平模式(默认)
  当以非公平初始化时,读锁和写锁的获取的顺序是不确定的。非公平锁主张竞争获取,可能会延缓一个或多个读或写线程,但是会比公平锁有更高的吞吐量

公平模式
  当以公平模式初始化时,线程将会以队列的顺序获取锁。当当前线程释放锁后,等待时间最长的写锁线程就会被分配写锁;或者有一组读线程组等待时间比
       写线程长,那么这组读线程组将会被分配读锁。

设置是否公平是通过构造,默认空参是不公平,有参false不公平,true公平

是可重入锁
个线程获取多少次锁,就必须释放多少次锁。这对于内置锁(synchronized)也是适用的 

锁降级
   从写锁变成读锁,该类支持锁降级
   为什么需要锁降级呢,释放后在获取读锁读锁不行吗,
   场景是:当获得了写锁,修改了数据后,先查看修改的数据,如果释放了锁,那么在多线程的情况下,有可能会有线程获得了写锁,
         再次对数据进行了操作,那么我们就无法得知上次修改的值了,使用锁降级,没有释放锁,其他线程获取不到写锁,
         然后在获取读锁,去读取数据,这样保证了数据被修改后,可以查看被修改的数据
         
锁升级
   从读锁编程写锁,但是 ReentranlReadWriteLock是不支持锁升级的

读锁使用共享模式
写锁使用独占模式


在这里插入图片描述

  ReentrantReadWriteLock实现的原理依然是内部类Sync继承AQS类,实现方式也是对state及waitStatus值进行操作。
  实现读写锁需要分别记录读锁状态和写锁状态,并且等待列队中需要区别处理两种加锁操作
  使用state值同时记录读写锁状态是将int类型的state变量分为高16位和低16位
  读写状态的发生操作的就是state值高16位和低16位的改变,这样就类似于ReentrantLock,只是读的过程可以允许多个线程同时发生,
  不需要加入到等待列队中,而写则依然需要加入等待列队
构造
 public class ReentrantReadWriteLock
            implements ReadWriteLock, java.io.Serializable {

        //ReadLock是ReentrantReadWriteLock中的静态内部类,它是读取锁的实现
        private final ReentrantReadWriteLock.ReadLock readerLock;

        //WriteLock是ReentrantReadWriteLock中的静态内部类,它是写入锁的实现
        private final ReentrantReadWriteLock.WriteLock writerLock;

        //Sync是ReentrantReadWriteLock中的静态内部类,它继承了AQS
        //它是读写锁实现的重点,后面深入分析
        final Sync sync;

        //默认使用非公平策略创建对象
        public ReentrantReadWriteLock() {
            this(false);
        }

        //fair置顶指定创建的锁是公平锁还是非公平锁
        public ReentrantReadWriteLock(boolean fair) {
            //FairSync和NonfairSync都继承自Sync,它们主要提供了对读写是否需要被阻塞的检查方法
            sync = fair ? new FairSync() : new NonfairSync();
            readerLock = new ReadLock(this);
            writerLock = new WriteLock(this);
        }
    }
 Sync类是实现读写锁的重点,之前讲到的很多基于AQS实现的锁机制都会有一个内部类Sync这个类中
 有不同的实现会有不同的功能我们看下读写锁中Sync内部的实现
   //继承了AQS
    abstract static class Sync extends AbstractQueuedSynchronizer {
        //常量值
        static final int SHARED_SHIFT   = 16;

        //左移16位后,二进制值是10000000000000000,十进制值是65536
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);

        //左移16位后再减一,十进制值是65535
        //这个常量值用于标识最多支持65535个递归写入锁或65535个读取锁
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;

        //左移16位后再减一,二进制值是1111111111111111
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

        //用于计算持有读取锁的线程数
        static int sharedCount(int c) {
            //无符号右移动16位
            //如果c是32位,无符号右移后,得到是高16位的值
            return c >>> SHARED_SHIFT; 
        }
        
        //用于计算写入锁的重入次数
        static int exclusiveCount(int c) {
            //如果c是32位,和1111111111111111做&运算,得到的低16位的值
            return c & EXCLUSIVE_MASK; 
        }

        //用于每个线程持有读取锁的计数
        static final class HoldCounter {
            //每个线程持有读取锁的计数
            int count = 0;

            //当前持有读取锁的线程ID
            //这里使用线程ID而没有使用引用,避免垃圾收集器保留,导致无法回收
            final long tid = Thread.currentThread().getId();
        }

        //通过ThreadLocal维护每个线程的HoldCounter
        static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            //这里重写了ThreadLocal的initialValue方法
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }

        //当前线程持有的可重入读取锁的数量,仅在构造方法和readObject方法中被初始化
        //当持有锁的数量为0时,移除此对象
        private transient ThreadLocalHoldCounter readHolds;

        //成功获取读取锁的最近一个线程的计数
        private transient HoldCounter cachedHoldCounter;

        //第一个获得读锁的线程
        private transient Thread firstReader = null;
        //第一个获得读锁的线程持有读取锁的次数
        private transient int firstReaderHoldCount;

        Sync() {
            //构建每个线程的HoldCounter
            readHolds = new ThreadLocalHoldCounter();
            setState(getState()); // ensures visibility of readHolds
        }
    }

读锁的加锁

 public void lock() {
        sync.acquireShared(1);
    }
    //调用AQS的acquireShared(int arg)方法
    public final void acquireShared(int arg) {
        //tryAcquireShared(arg)获取读锁,如果获取失败即state值高16位的获取值小于0
        //则证明在执行写入操作读锁应该是阻塞状态,需要将当前读线程加入到列队中去
        if (tryAcquireShared(arg) < 0)
            //加入到等待列队中
            doAcquireShared(arg);
    }
 .。。。。。下面暂且。。。。。,资料不足
  无锁状态下加锁
        线程调用acquireShared函数,首先使用tryAcquireShared函数判断共享锁是否可获取成功,
       由于当前为无锁状态则获取锁一定成功(如果同时多个线程在读锁进行竞争,则只有一个线程能够直接获取读锁,
      其他线程需要进行fullTryAcquireShared函数继续进行锁的获取
      
 有锁状态获取读锁
   
     有锁状态则需要分情况讨论。其中需要分当前被持有的锁是读锁还是写锁,并且每种情况需要区分等待队列中是否有等待节点。
     
        已有读锁且等待队列为空,
           此时线程申请读锁,首先调用readerShouldBlock函数进行判断,该函数根据当前锁是否为公平锁判断规则稍有不同。
           如果为非公平锁,则只需要当前第一个  等待节点不是写锁就可以尝试获取锁(考虑第一点为写锁主要为了防止写锁“饿死”);
           如果是公平锁则只要有等待节点且当前锁不为重入就需要等待。
           则当前线程使用CAS对读锁计数进行增加(同上文,如果同时多个线程在读锁进行竞争,则只有一个线程能够直接获取读锁,其他线程需要进入
           fullTryAcquireShared函数继续进行锁的获取)。在成功对读锁计数器进行增加后,当前线程需要继续对当前线程持有读锁的计数进行增加
           此时分为两种情况:
               1当前线程是第一个获取读锁的线程,此时由于第一个获取读锁的线程已经通过firstReader及firstReaderHoldCount两个变量进行存储,
               则仅仅需要将firstReaderHoldCount加1即可;
               
              2 当前线程不是第一个获取读锁的线程,则需要使用readHolds进行存储,readHolds是ThreadLocal的子类,
               通过readHolds可获取当前线程对应的HoldCounter类的对象,该对象保存了当前线程获取读锁的计数。考虑程序的局部性原理,
               又使用cachedHoldCounter缓存最近使用的HoldCounter类的对象,如在一段时间内只有一个线程请求读锁则可加速对读锁获取的计数。


部分内容来自   https://zhuanlan.zhihu.com/p/87590807
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值