读写锁的简单认知

为什么需要读写锁

  简单来说,一般的锁只能让一个线程进行占用,别的线程只能进行等待,但是在实际开发的过程中,我们往往都是读多写少的情况,希望可以一起进行读,写的时候可以进行加锁。总结起来就是:读读不互斥,读写互斥,写写互斥

jdk版本的读写锁

  jdk版本提供了一个ReadWriteLock类,jdk中的主要实现类为ReentrantReadWriteLock,里面有两个类ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock进行创建读锁和写锁

 ReentrantReadWriteLock的核心是由一个基于AQS的同步器Sync构成,然后由其扩展 出 ReadLock(读锁),WriteLock(写锁)

ReentrantReadWriteLock构造函数

   public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
        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;

        /** Returns the number of shared holds represented in count  */
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        /** Returns the number of exclusive holds represented in count  */
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }


     /**
     * The synchronization state.
     */
    private volatile int state;

    /**
     * Returns the current value of synchronization state.
     * This operation has memory semantics of a {@code volatile} read.
     * @return current state value
     */
    protected final int getState() {
        return state;
    }

  从上面的代码可以看出来,ReentrantReadWriteLock是通过state这个int字段的高低16位来进行表示共享锁和独占锁的数量,高16位表示共享锁的数,低16位表示独占锁的数量

  

读锁的获取

//获取读锁开始
public void lock() {
   sync.acquireShared(1);
}


public final void acquireShared(int arg) {
   //尝试进行获取锁
  if (tryAcquireShared(arg) < 0)
     //获取失败之后进行添加到AQS队列中
     doAcquireShared(arg);
}
tryAcquireShared获取读锁
protected final int tryAcquireShared(int unused) {
   //获取当前线程 
   Thread current = Thread.currentThread();
   //获取当前的状态 读写锁中通过state状态进行判断加锁的状态
   int c = getState();
   //进行判断是否是独占锁,并且独占锁的线程是当前线程
   // 如果是独占锁 并且独占锁的线程不是当前线程就直接返回-1
   // 返回-1 就直接进入队列进行排队的操作
   if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
         return -1;
   //返回持久共享锁的数量
   int r = sharedCount(c);
   /***
    *  readerShouldBlock判断是否可以获取共享锁
    *  r < MAX_COUNT 共享锁的数量是否小于最大的锁数量
    *  采用cas操作进行设置共享锁的数量
    *  设置读锁的数量
    ***/
   if (!readerShouldBlock() &&r < MAX_COUNT &&
          compareAndSetState(c, c + SHARED_UNIT)) {
       if (r == 0) {
            //读锁数量=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);
}
fullTryAcquireShared
        final int fullTryAcquireShared(Thread current) {
            /*
             * This code is in part redundant with that in
             * tryAcquireShared but is simpler overall by not
             * complicating tryAcquireShared with interactions between
             * retries and lazily reading hold counts.
             */
            HoldCounter rh = null;
            // 最外层是一个死循环
            for (;;) {
                //获取当前锁的状态
                int c = getState();
                //存在写锁并且写锁不是当前线程直接返回
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;

                    // else we hold the exclusive lock; blocking here
                    // would cause deadlock.
                    
                } else if (readerShouldBlock()) {
                    //如果当前线程是重入锁
                    // Make sure we're not acquiring read lock reentrantly
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {

                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != 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 != getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }

读锁的释放

    public void unlock() {
     sync.releaseShared(1);
    }
   
   public final boolean releaseShared(int arg) {
        //进行读锁的释放
        if (tryReleaseShared(arg)) {
            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;
            }
//cas操作进行更改读锁的状态和数量
            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;
            }
        }

写锁的获取 


//获取写锁 
public void lock() {
     sync.acquire(1);
 }


  public final void acquire(int arg) {
    //tryAcquire 尝试进行获取写锁
    // 获取写锁失败之后 进行添加到队列中
    // 将当前线程设置成interrupt状态
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
 protected final boolean tryAcquire(int acquires) {
          //获取当前线程  
            Thread current = Thread.currentThread();
          //获取当前锁的状态     
           int c = getState();
         //获取写锁的数量
          int w = exclusiveCount(c);
          // c!=0 证明当前要么存在读锁 要么存在写锁  
          if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                // 存在锁,写锁数量为0 那么证明 读锁数量不为0 并且 当前线程还不是加锁线程
                //直接返回失败
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                //数量超限制直接抛出异常
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                // 执行到这里 证明存在锁 并且是一个写锁 并且当前线程就是加锁的线程
                //而且加锁的数量也没超过最大的数量 就可以直接进行加锁
                //可以直接进行修改锁的数量。不需要进行cas之类的操作 因为都是同一个线程
                setState(c + acquires);
                return true;
            }
            //writerShouldBlock对于非公平模式直接返回fasle,对于公平模式则线程需要排队
            // 因此在这里进行阻塞
            
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

写锁的释放


public void unlock() {
  sync.release(1);
}

 public final boolean release(int arg) {
   if (tryRelease(arg)) {
       //写锁释放成功之后 进行唤醒队列中第一个元素
       Node h = head;
       if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
        return true;
    }
    return false;
}

tryRelease方法

    protected final boolean tryRelease(int releases) {
      // 判断当前线程是否可以进行写锁的释放 如果不可以
      //直接抛出异常
      if (!isHeldExclusively())
          throw new IllegalMonitorStateException();
      //写锁数量的更新
      int nextc = getState() - releases;
      boolean free = exclusiveCount(nextc) == 0;
     //当写锁的数量为0的时候 将写锁的拥有者的线程设置为null;
      if (free)
       setExclusiveOwnerThread(null);
      setState(nextc);
      return free;
    }


  protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

redis分布式读写锁

redis分布式锁的使用的redission框架进行读写锁的配置,

        RReadWriteLock rReadWriteLock = redissonClient.getReadWriteLock("rwlock");
        RLock readLock = rReadWriteLock.readLock();
        readLock.lock();
        readLock.unlock();
        
        RLock writeLock = rReadWriteLock.writeLock();
        writeLock.lock();
        writeLock.unlock();


#读写锁的初始化
public class RedissonReadWriteLock extends RedissonExpirable implements RReadWriteLock {

    public RedissonReadWriteLock(CommandAsyncExecutor commandExecutor, String name) {
        super(commandExecutor, name);
    }

    @Override
    public RLock readLock() {
        return new RedissonReadLock(commandExecutor, getName());
    }

    @Override
    public RLock writeLock() {
        return new RedissonWriteLock(commandExecutor, getName());
    }

}

redission分布式锁的读锁

分布式锁读锁的加锁设置

   <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        return commandExecutor.syncedEval(getRawName(), LongCodec.INSTANCE, command,
                                "local mode = redis.call('hget', KEYS[1], 'mode'); " +
                                //获取 hash列表中的mode字段的值
                                "if (mode == false) then " +
                                 //如果hash表中的mode的值为false的时候 设置mode值为read
                                  "redis.call('hset', KEYS[1], 'mode', 'read'); " +                                //设置hash表中的 锁的添加次数为1
                                  "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                                 // 设置锁的缓存key
                                  "redis.call('set', KEYS[2] .. ':1', 1); " +
                                 //设置过期时间
                                 "redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]); " +                               //设置过期时间
                                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                                  "return nil; " +
                                "end; " +
                                // 如果mode值为read
                                // 如果mode值为write 并且加写锁的线程为当前线程
                                "if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then " +
                                  //添加锁的数量  
                                  "local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); " + 
                                  "local key = KEYS[2] .. ':' .. ind;" +
                                  "redis.call('set', key, 1); " +
                                  "redis.call('pexpire', key, ARGV[1]); " +
                                  "local remainTime = redis.call('pttl', KEYS[1]); " +
                                  "redis.call('pexpire', KEYS[1], math.max(remainTime, ARGV[1])); " +
                                  "return nil; " +
                                "end;" +
                                "return redis.call('pttl', KEYS[1]);",
                        Arrays.<Object>asList(getRawName(), getReadWriteTimeoutNamePrefix(threadId)),
                        unit.toMillis(leaseTime), getLockName(threadId), getWriteLockName(threadId));
    }

从上面的代码可以看出来,redission提供的读写锁并不是完全的互斥,当同一个线程先获取写锁还是可以再次获取读锁的

分布式锁读锁的释放操作

 protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        String timeoutPrefix = getReadWriteTimeoutNamePrefix(threadId);
        String keyPrefix = getKeyPrefix(threadId, timeoutPrefix);

        return commandExecutor.syncedEval(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "local mode = redis.call('hget', KEYS[1], 'mode'); " +
                 //获取当前读写锁的模式
                 // 如果锁为false 直接进行发送锁释放的消息     
                 "if (mode == false) then " +
                    "redis.call(ARGV[3], KEYS[2], ARGV[1]); " +
                    "return 1; " +
                "end; " +
                //判断当前是否存在 如果不存在直接返回null    
                "local lockExists = redis.call('hexists', KEYS[1], ARGV[2]); " +
                "if (lockExists == 0) then " +
                    "return nil;" +
                "end; " +
                    
                //对读锁的数量进行减1的操作
                "local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1); " + 
                //读锁的数量到最后变成0 就直接进行删除key的操作
                "if (counter == 0) then " +
                    "redis.call('hdel', KEYS[1], ARGV[2]); " + 
                "end;" +
                // 删除锁对应的加锁记录
                "redis.call('del', KEYS[3] .. ':' .. (counter+1)); " +
                
                // 获取map中的key的数量
                "if (redis.call('hlen', KEYS[1]) > 1) then " +
                    "local maxRemainTime = -3; " + 
                     // 获取map中key的集合
                    "local keys = redis.call('hkeys', KEYS[1]); " + 
                    //针对map的key进行遍历操作
                    "for n, key in ipairs(keys) do " + 
                        // 获取加锁的次数
                        //进行更新锁的过期时间
                        "counter = tonumber(redis.call('hget', KEYS[1], key)); " + 
                        "if type(counter) == 'number' then " + 
                            "for i=counter, 1, -1 do " + 
                                //获取锁的剩余时间
                                "local remainTime = redis.call('pttl', KEYS[4] .. ':' .. key .. ':rwlock_timeout:' .. i); " + 
                                "maxRemainTime = math.max(remainTime, maxRemainTime);" + 
                            "end; " + 
                        "end; " + 
                    "end; " +
                     //如果剩余的时间大于0 更新锁的过期时间为最大的过期时间       
                    "if maxRemainTime > 0 then " +
                        "redis.call('pexpire', KEYS[1], maxRemainTime); " +
                        "return 0; " +
                    "end;" + 
                     //如果是模式是写锁 直接返回0   
                    "if mode == 'write' then " + 
                        "return 0;" + 
                    "end; " +
                "end; " +
                    
                //走到这里 证明了锁已经释放 并且没有别的线程持有这个锁了
                // 可以直接删除锁的key    
                "redis.call('del', KEYS[1]); " +        
                 // 发送unlock的消息
                "redis.call(ARGV[3], KEYS[2], ARGV[1]); " +
                "return 1; ",
                Arrays.<Object>asList(getRawName(), getChannelName(), timeoutPrefix, keyPrefix),
                LockPubSub.UNLOCK_MESSAGE, getLockName(threadId), getSubscribeService().getPublishCommand());
    }

redission分布式锁的写锁

redission写锁的添加

    <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        return commandExecutor.syncedEval(getRawName(), LongCodec.INSTANCE, command,
                //获取锁的模式           
                 "local mode = redis.call('hget', KEYS[1], 'mode'); " +

                       //判断mode的是否为false 
                            "if (mode == false) then " +
                                //如果是false 设置mode的值为write
                                  "redis.call('hset', KEYS[1], 'mode', 'write'); " +                                  
//设置当前锁的线程id
                                  "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                                   //设置过期时间
                                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                                  "return nil; " +
                              "end; " +
                                //判断mode是否为write
                              "if (mode == 'write') then " +
                                    //判断是否有当前线程添加的redis锁
                                  "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +                            
                                //存在进行加锁的次数进行+1
                                      "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +                                   
                              //获取当前锁的过期时间
                                      "local currentExpire = redis.call('pttl', KEYS[1]); " +                            
                               //设置过期时间为当前锁的过期时间
                                      "redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); " +
                                      "return nil; " +
                                  "end; " +
                                "end;" +
                                //返回当前锁的过期时间
                                "return redis.call('pttl', KEYS[1]);",
                        Arrays.<Object>asList(getRawName()),
                        unit.toMillis(leaseTime), getLockName(threadId));
    }

 redisssion分布式写锁的释放

protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "local mode = redis.call('hget', KEYS[1], 'mode'); " +
                //获取redis中的key中的mode的值
                "if (mode == false) then " +
                    //mode的值为false的时候 进行通知
                    "redis.call(ARGV[4], KEYS[2], ARGV[1]); " +
                    //返回1
                    "return 1; " +
                "end;" +
                // 如果mode为write 证明redis中已经加上了写锁
                "if (mode == 'write') then " +
                    
                //判断是否有当前线程添加的锁
                    "local lockExists = redis.call('hexists', KEYS[1], ARGV[3]); " +
                    //没有的话 就直接返回
                    "if (lockExists == 0) then " +
                        "return nil;" +
                    "else " +
                    // 进行获取加锁的次数
                        "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                  //加锁的次数大于0
                        "if (counter > 0) then " +
                      //设置key的过期时间为internalLockLeaseTime的值
                            "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                            "return 0; " +
                        "else " +
                        //加锁的次数等于0 不存在小于0的情况 直接删除绑定线程的key
                            "redis.call('hdel', KEYS[1], ARGV[3]); " +
                       // 判断此时hash结构中的key的数量 
                            "if (redis.call('hlen', KEYS[1]) == 1) then " +
                       //如果数量等于1 证明只有一个加锁 可以直接删除这个key了
                                "redis.call('del', KEYS[1]); " +
                                "redis.call(ARGV[4], KEYS[2], ARGV[1]); " +
                            "else " +
                            //到这块的话,证明写锁已经全部释放完毕了 
                            //但是此时还有一个加锁的 那么此时加的这个锁就不是写锁 
                            //只能是读锁,所以将redis中的锁设置为读锁

                                // has unlocked read-locks
                                "redis.call('hset', KEYS[1], 'mode', 'read'); " +
                            "end; " +
                            "return 1; "+
                        "end; " +
                    "end; " +
                "end; "
                + "return nil;",
        Arrays.<Object>asList(getRawName(), getChannelName()),
        LockPubSub.READ_UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId), getSubscribeService().getPublishCommand());
    }

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小园子的小菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值