AQS的应用:可重入锁和读写锁

一、Lock接口

1、Lock

​ Lock跟Synchronized一样,都是用在多线程中用来控制并发,保证共享资源安全的一种同步机制。

Synchronized是隐式的加锁/解锁;而Lock是显示的加锁/解锁,也就是说我们使用的时候需要先lock.lock()锁定资源,执行逻辑,用完后在lock.unlock()释放资源。

2、特性

​ Lock有Synchronized没有的特性,这几个特性使我们在开发中更方便灵活

​ 1、非阻塞性获取同步状态。tryLock方法可以立即返回是否获取到同步状态,先尝试获取一次,有就有,没有就没有,不用去等待;

​ 2、支持中断。自己释放不了同步状态,可以被中断,通过中断信号跳出某种状态,拿到同步状态干到一半干不下去就不干了释放同步状态给别人用吧。lockInterruptbly()是响应中断方法。

​ 3、支持超时。到一定时间再干不完就不干了,释放同步状态。支持带时间参数的tryLock()。

3、用法

lock.lock()加锁;lock.unlock()解锁。

/**
 * Lock的标准用法,注意两点:
 * 1、try外上锁
 * 2、finlly里解锁
 */
public class LockUseCase {
    static Lock lock = new ReentrantLock();//定义锁
    public static void main(String[] args) {
        lock.lock();//try外上锁
        try{
            //todo 业务逻辑
        }finally {
            lock.unlock();//解锁
        }
    }
}

为什么要将lock放到try外面呢?因为lock方法可能会抛出异常,在try内使用,加锁的时候如果抛出异常加锁失败就进入finally里去解锁,加锁都没成功,解锁自然也会抛出异常;而在try外使用,如果加锁抛出异常就不走finally了,没有加锁自然也不会解锁,当然没问题。lock()是自定义同步方法,跟进源码会发现不管是公平模式还是非公平模式都有一大串的代码,要尝试获取同步状态,获取不成功还要入队,等待,被唤醒等操作,这过程也存在抛异常的可能,lock方法不去捕获异常,遇到问题就直接中断了,不会影响其他线程,这样更安全。总之,在try外使用就是防止lock抛异常时出现问题。

二、可重入锁 ReentrantLock

1、ReentrantLock

​ 由名字可以看出,他跟synchronized一样,同一个线程可以多次拿锁,并且它有两种模式获取锁:公平模式获取同步状态和非公平模式获取同步状态。公平模式就是按入队顺序,挨个出队去拿同步状态,非公平模式是争抢式拿同步状态,谁抢到就是谁的。效率上肯定是非公平模式更高,默认就是非公平模式。

2、源码

/***
 * 可重入锁。独占式锁的代表,AQS的一个应用
 * 它支持公平和非公平获取同步状态
 */
public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;
    //自定义同步器,继承AQS。这是一个大方面的抽象类同步器,下面再细分公平和非公平同步器
    abstract static class Sync extends AbstractQueuedSynchronizer {
        abstract void lock();//定义抽象方法,子类去具体实现
        /***
         * 定义一个非公平获取同步状态的方法,由子类非公平同步器调用
         * 公平获取同步状态的方法在AQS里已经存在了,子类直接重写就可以了
         * 非阻塞式,立即返回结果;是否获取成功
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {//同步状态没有被占用,是空闲的,尝试用cas获取同步状态
                if (compareAndSetState(0, acquires)) {//cas获取成功
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果当前线程就是获取了同步状态的线程,说明这是重新进入(可重入锁实现逻辑),可以获取同步状态
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
        
        //重写方法:独占式释放同步状态
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
        //持有同步状态的线程是否是当前线程
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
    }

    //自定义的非公平模式同步器
    static final class NonfairSync extends Sync {
        //自定义方法:阻塞式获取同步状态,既然式获取同步状态了,肯定式同步资源没被占用,所以是0
        final void lock() {
            //下面就是争抢了,谁先cas成功,就把同步资源给那个线程,cas不成功的只能进入等待队列了
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
            }else {
                acquire(1);//调用模板方法:aqs的独占式获取同步状态,模板方法会调用下面重写的那个方法
            }
        }
        //重写方法:独占式获取同步状态
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    
    //自定义的公平模式同步器
    static final class FairSync extends Sync {
        //自定义方法:阻塞式获取同步状态,公平模式原理就是从等待队列里挨个取,先进先出原则
        final void lock() {
            acquire(1);//调用模板方法:aqs的独占式获取同步状态,模板方法会调用下面重写的那个方法
        }
        //重写方法:独占式获取同步状态
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {//没有前驱节点并且能够获取同步资源
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                setState(nextc);
                return true;
            }
            return false;
        }
    }
    //两个构造函数,默认是非公平模式
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    //参数可以指定用哪种模式
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    public void lock() {
        sync.lock();
    }
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
    public void unlock() {
        sync.release(1);
    }
}

三、读写锁 ReentrantReadWriteLock

1、读写锁

​ 现实中大部分场景都是读多写少的,读写锁正是这种背景下诞生。读写锁分为读锁和写锁,读锁是共享式获取同步状态,同一时刻可以有多个线程获的同步状态;写锁是独占式获取同步状态,是排他锁,同一时刻只能有一个线程获取同步状态。

​ 如果写线程正在执行写操作,其他线程都会被阻塞,禁止获取读或写操作,原因要是保证变量的可见性。也就是说只要有线程拿着读锁,写锁一定被获取不到。

​ 一个同步状态的变量要维护两个状态(读写)是通过对int类型变量切分实现的,同步状态是一个int类型变量,是占32位,把他分成高16位代表读,低16位代表写。这样读写状态的改变通过int类上高低16位的位运算计算就可以实现了。

2、锁降级

锁降级是指由写锁降级为读锁的过程。写是排他性的,只能有一个进行,所以级别比较高;读是共享的,可以多个同时进行,级别低。

锁降级的过程是先拿到写锁,再去拿读锁,再释放写锁。

​ 为啥会有这个过程,不是直接释放写锁去拿读锁?如果直接释放写锁,可能刚释放会被其他线程拿到写锁,其他线程修改了你的值,你再去用就不是你想要的值了,如果在持有写锁的过程中再拿读锁,这样即使释放了写锁,其他线程也无法拿到写锁,因为你本身在占据着读锁,这保证了数据变量的可见性。

3、用法

public class LockUseCase {
    static ReadWriteLock rwLock = new ReentrantReadWriteLock();
    static Lock readLock = rwLock.readLock();
    static Lock writeLock = rwLock.writeLock();
    public static void readTest(){
        readLock.lock();
        try{
            //todo 业务逻辑
        }finally {
            readLock.unlock();
        }
    }
    public static void writeTest(){
        writeLock.lock();
        try{
            //todo 业务逻辑
        }finally {
            writeLock.unlock();
        }
    }
}

4、部分源码

/**
 * 可重入读写锁。支持读锁(共享式获取同步状态)和写锁(独占式获取同步状态),也是AQS的应用
 */
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    private final  ReadLock readerLock;
    private final  WriteLock writerLock;
    final Sync sync;
    public ReentrantReadWriteLock() {
        this(false);
    }
    /***
     * 构造函数就确定了是否是公平模式并且创建了读锁实例和写锁实例
     * @param fair
     */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new  FairSync() : new  NonfairSync();
        readerLock = new  ReadLock(this);
        writerLock = new  WriteLock(this);
    }
    public  WriteLock writeLock() { return writerLock; }
    public  ReadLock  readLock()  { return readerLock; }
    /**
     * 内部类形式的自定义同步器
     * 一个state状态怎么区分读写的呢?通过高低16位,位运算来实现.
     */
    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; }//独占式同步资源数量
        static final class HoldCounter {
            int count = 0;
            final long tid = getThreadId(Thread.currentThread());
        }
        static final class ThreadLocalHoldCounter extends ThreadLocal< Sync.HoldCounter> {
            public  Sync.HoldCounter initialValue() {
                return new  Sync.HoldCounter();
            }
        }
        //重写方法:释放独占式同步资源
        protected final boolean tryRelease(int releases) {
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }

        /***
         * 重写写锁(独占式)获取同步状态方法
         * @param acquires
         * @return
         */
        protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();//获取同步状态
            int w = exclusiveCount(c);//获取独占(写锁)它的数量
            if (c != 0) {//同步状态已经被拿到了,有可能是它或者其他线程
                //当前线程不是已经获取写锁的线程,返回获取失败
                if(current != getExclusiveOwnerThread()){
                    return false;
                }
                //已经获取了同步状态,但是写锁呢又是0,所以这个同步状态是读锁的
                //存在读锁,也返回失败
                if (w == 0) {
                    return false;
                }
                //给同步状态加1计数,是当前线程重入的
                setState(c + acquires);
                return true;
            }
            //还没线程占有同步状态
            //如果写锁被阻塞了,已经被别人拿了写锁就返回失败
            if(writerShouldBlock()){
                return false;
            }
            //如果cas设置同步状态失败,返回失败
            if (!compareAndSetState(c, c + acquires)){
                return false;
            }
            //否则,设置上当前线程后返回成功
            setExclusiveOwnerThread(current);
            return true;
        }
        /***
         * 重写 读锁(共享锁)获取同步状态方法
         * @param unused
         * @return
         */
        protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();//一个同步状态通过“按位切割”高低16位运算记录读状态和写状态
            //独占锁(写锁)已经被获取了,并且还不是本线程
            if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) {
                return -1;
            }
            int r = sharedCount(c);//获取共享(读锁)得同步状态数量
            if (!readerShouldBlock() && r < MAX_COUNT &&  compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {//如果读之前没被获取过
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                     Sync.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);
        }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值