实现一个读写锁

1 篇文章 0 订阅

package aqs;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

public class RetreeWirterReadLock {
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
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; }

public AtomicInteger state = new AtomicInteger(0);

public AtomicInteger getState() {
    return state;
}

private volatile Thread owner = null;
private volatile LinkedBlockingQueue<WaitNode> waiters = new LinkedBlockingQueue();

public void lock(){
    // 尝试获取锁, 获取锁失败
    if(!tryLock()){
        // 添加到队列里
        waiters.offer(new WaitNode(Thread.currentThread(), 0, 0));
        // 避免假唤醒
        while (true){
            // 当线程重新苏醒,重新开始抢锁, 抢锁的条件,等待的队列的头部是自己,
            WaitNode head = waiters.peek();
            // 是自己
            if(head != null && head.getThread() == Thread.currentThread()){
                // 头部是自己。开始获取到锁
                if(tryLock()){
                    // 获取锁成功 ,移除头部,并返回
                    waiters.poll();
                    return;
                }else {
                    // 获取锁失败,场景: 有一个新的线程和头线程一起抢锁,头线程失败,再度休眠
                    LockSupport.park(Thread.currentThread());
                }
            } else {
                // 不是自己,休眠起来,触发的场景: 1. 进来抢锁失败,并且头部不是自己,只能把自己休眠起来,2 . 线程的伪唤醒,使用循环,重新把自己休眠起来
                LockSupport.park(Thread.currentThread());
            }
        }
    }
}

/**
 * 尝试获取到一个锁
 * @return
 */
private boolean tryLock(){
    int countNum = state.get();
    // 转换成写锁的count 存在读锁,获取写锁失败
    if(sharedCount(countNum) > 0){
        return false;
    }

    // 转换成写锁的count
    int count = exclusiveCount(countNum);
    // 没有锁
    if(countNum == 0){
        // 说明下int 的前16位存放共享锁信息,后16位存放独占锁信息, 所以独占锁每次获取到一次都是 + 1
        if(state.compareAndSet(0, 1)){
            owner = Thread.currentThread();
            return true;
        }
    }else if(owner == Thread.currentThread()){
        // 说明下int 的前16位存放共享锁信息,后16位存放独占锁信息, 所以独占锁每次获取到一次都是 + 1
        // 重入锁的情况 count +1
        state.compareAndSet(countNum, countNum + 1);
        return true;
    }
    return false;
}

public boolean unLock(){
    // 释放锁
    if(tryUnLock()){
        // 释放锁成功, 获取到等待队列中的头线程
        WaitNode head = waiters.peek();
        if(head != null){// 若头部有等待的线程,则将这个线程唤醒
            // 唤醒头线程
            LockSupport.unpark(head.getThread());
        }
        return true;
    }

    return false;
}

/**
 * 尝试释放独占锁,只会有一个线程执行到这个方法
 * @return
 */
private boolean tryUnLock(){
    // 前置检查, 当前的线程是否与owner是一个
    if(Thread.currentThread() != owner){
        throw new RuntimeException("异常情况");
    }

    //释放锁分为两个情况,释放锁次数,次数为0, 释放锁次数次数不为0(重入的情况)
    int countNum = state.get();
    // 转换成写锁的count
    int count = exclusiveCount(countNum);
    state.set(count - 1);// 这里不使用cas操作,是因为独占锁,释放锁,只会有一个线程进行操作
    if(state.get() == 0){
        //锁释放完全
        owner = null;
        return true;
    }

    return false;
}

public void lockShare(){
    // 获取锁失败
    if(!tryLockShare()){
        // 将线程放到队列中
        // 添加到队列里
        waiters.offer(new WaitNode(Thread.currentThread(), 1, 0));
        while (true){
            // 获取到队列的头
            WaitNode head = waiters.peek();
            if(head != null && head.getThread() == Thread.currentThread()){
                //开始尝试获取锁
                if(tryLockShare()){// 获取锁成功
                    // 移除头部
                    waiters.poll();
                    // 唤醒接下来的读锁
                    WaitNode headNew = waiters.peek();
                    if(headNew != null && headNew.getType() == 1){
                        LockSupport.unpark(headNew.getThread());
                    }
                    return;
                } else {
                    // 获取锁失败,再度把自己休眠
                    LockSupport.park(Thread.currentThread());
                }
            }else {
                // 头不是自己,休眠自己
                LockSupport.park(Thread.currentThread());
            }
        }
    }
}

private boolean tryLockShare(){
    // 获取到共享锁,这里可能存在多个读锁同时修改state的值,使用循环cas操作修改state ,
    while (true){
        int count =  state.get();
        // 转换成独占锁个数
        int countWriter = exclusiveCount(count);
        //  判断是否具有独占锁
        if(countWriter != 0){
            return false;
        }

        // 说明下int 的前16位存放共享锁信息,后16位存放独占锁信息, 所以共享锁每次获取到一次都是 + 65536
        if(state.compareAndSet(count, count + SHARED_UNIT)){
            return true;
        }
    }
}

public boolean unlockShare(){
    //释放锁成功
    if(tryUnlockShare()){
        WaitNode head = waiters.peek();
        if(head != null){
            LockSupport.unpark(head.getThread());
        }
        return true;
    }

    return false;
}

public boolean tryUnlockShare(){
    while (true){
        int count =  state.get();
        if(state.compareAndSet(count, count - SHARED_UNIT)){
            return state.get() == 0;
        }
    }
}

}

AQS就是描述上面的过程的技术,主要包括这些方法

  1. 获取独占锁:主要使用了尝试获取独占锁的方法,失败,就把这些锁的线程进行等待(主要实现的是线程间的挂起逻辑)
  2. 尝试获取独占锁: 通过判断独占锁的次数以及owner来判断是否有独占锁,若有共享锁也不能获取,使用CAS修改state
  3. 释放独占锁:判断state是否为0,为0进行唤醒线程(主要实现的是线程的唤醒逻辑)
  4. 尝试释放独占锁:进行CAS修改state,
  5. 获取共享锁:主要使用了尝试获取共享锁的方法,失败,就把这些锁的线程进行等待(主要实现的是线程间的挂起逻辑),成功,就唤醒队列中的读线程
  6. 尝试获取共享锁:通过判断独占锁的次数来判断是否有独占锁,有独占锁不能获取,有共享锁能获取,使用CAS修改state
  7. 释放共享锁 判断state是否为0,为0进行唤醒线程(主要实现的是线程的唤醒逻辑)
  8. 尝试释放共享锁 进行CAS修改state,
    在这里插入图片描述
    在上面的实现,需要用一个变量同时存储读锁次数,写锁次数在能处理读锁,和写锁同时竞争锁的情况。以下是这个同步状态的设计方案,就可以在一个变量上保存了读锁次数,和写锁次数,这是设计读写锁的必要条件
    在这里插入图片描述
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值