Java并发 - 读写锁与AQS简单了解

读写锁 ReadWriteLock

  • 概念
    维护一对关联锁,一个只用于读操作,一个只用于写操作;
    读锁可以由多个读线程同时持有,写锁是排他的。同一时间,两把锁不能被不同线程持有
    目的是为了将读写分开,因为如果不分开的话,那么多个读锁想要同时获取的时候,还是需要等待,但是此时锁住的内容是没有改变的,这样就缇欧生了提升系统运行的效率。

例子:

package lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLock_Demo {
    //不希望读写的内容都不一样,需要加入锁机制
    volatile long i = 0;

    Lock lock = new ReentrantLock();

    ReadWriteLock rwLock = new ReentrantReadWriteLock();

    // 使用读写锁的方式
    public void read() {
        rwLock.readLock().lock();

        long iValue = i;

        rwLock.readLock().unlock();
    }

    public void write() {
        rwLock.writeLock().lock();

        i++;

        rwLock.writeLock().unlock();
    }

    // 非读写锁的方式
//    public void read() {
//        lock.lock();
//
//        long a = i;
//
//        lock.unlock();
//    }
//
//    public void write() {
//        lock.lock();
//
//        i++;
//
//        lock.unlock();
//    }
}

  • 适用场景
    适合读取操作多于写入操作的场景,改进互斥锁的性能,
    比如:集合的并发线程安全性改造、缓存组件。

HashMap使用读写锁实现缓存,【读多写少】的例子

package lock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @Author: Neco
 * @Description: HashMap使用读写锁实现缓存,读多写少的例子
 * @Date: create in 2022/6/20 17:59
 */
public class HashMap_Demo {

    // 存放值的map
    private final Map map = new HashMap<>();

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 读写锁
    private final Lock readLock = readWriteLock.readLock(); // 读锁
    private final Lock writeLock = readWriteLock.writeLock(); // 写锁

    // 获取数据
    public Object get(String key) {
        readLock.lock();
        try {
            return map.get(key);
        } finally {
            readLock.unlock();
        }
    }

    // 放入缓存
    public void put(String key, Object object) {
        writeLock.lock();
        try {
            map.put(key, object);
        } finally {
            writeLock.unlock();
        }
    }

    // 获取所有的key
    public Object[] keys() {
        readLock.lock();
        try {
            return map.keySet().toArray();
        } finally {
            readLock.unlock();
        }
    }

    // 清空缓存
    public void clear() {
        writeLock.lock();
        try {
            map.clear();
        } finally {
            writeLock.unlock();
        }
    }

    // 其他的一些方法略……

}

  • 锁降级
    指的是写锁降级成为读锁。持有写锁的同时,再获取读锁,随后释放写锁的过程。写锁是线程独占,读锁是共享,所以写->读是降级。(读->写,是不能实现的)

读写锁的实现代码 - 理论上实现的一个例子

  • 锁的代码
package lock.readwritelock1;

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

/**
 * @Author: Neco
 * @Description:
 * @Date: create in 2022/6/20 17:55
 */
public class NecoReadWriteLock {
    // 一个read的count
    AtomicInteger readCount = new AtomicInteger(0);
    // 一个write的count
    AtomicInteger writeCount = new AtomicInteger(0);

    // 独占锁 拥有者
    AtomicReference owner = new AtomicReference<>();

    // 等待队列
    public volatile LinkedBlockingQueue waiters = new LinkedBlockingQueue();

    // 获取独占锁
    public void lock() {
        int arg = 1;
        // 如果尝试,如果成功则方法正常退出,否则进行其他的处理
        if (!tryLock(arg)) {
            // 创建锁节点,并且标记为独占锁
            WaitNode waitNode = new WaitNode(Thread.currentThread(), 0, arg);
            // 置入等待队列
            waiters.offer(waitNode);

            // 循环尝试获取锁
            while (true) {
                // 获取头部节点线程
                WaitNode head = waiters.peek();
                // 如果头节点不为空且为当前线程
                if (head != null && head.thread == Thread.currentThread()) {
                    // 再次尝试获取 独占锁
                    if (!tryLock(arg)) {
                        // 若获取失败,挂起线程
                        LockSupport.park();
                        //若成功获取
                    } else {
                        //  将当前线程从队列头部移除,并退出方法
                        waiters.poll();
                        return;
                    }
                }
            }
        }
    }

    // 释放独占锁
    public void unlock() {
        int arg = 1;
        //尝试释放独占锁 若失败返回true,若失败...
        if (tryUnlock(arg)) {
            // 取出队列头部的元素
            WaitNode head = waiters.peek();
            if (head != null) {
                Thread th = head.thread;
                // 唤醒队列头部的线程
                LockSupport.unpark(th);
            }
        }
    }

    //尝试获取独占锁
    public boolean tryLock(int acquires) {
        // 如果 被读锁占用 readCount > 0,则返回false
        if (readCount.get() != 0) return false;
        // 获取 独占锁 状态 0 未被占用
        int wct = writeCount.get(); //
        // wct == 0,表示未被占用
        if (wct == 0) {
            // 设置值,抢锁
            if (writeCount.compareAndSet(wct, wct + acquires)) {
                // 抢锁成功,设置owner
                owner.set(Thread.currentThread());
                return true;
            }
            // 如果已经被占用,且占用的线程为当前线程,则进行重入操作
        } else if (owner.get() == Thread.currentThread()) {
            writeCount.compareAndSet(wct, wct + acquires);
            return true;
        }
        // 否则抢锁失败
        return false;
    }

    //尝试释放独占锁
    public boolean tryUnlock(int releases) {
        // 如果当前线程没有持有独占锁,则抛出异常
        if (owner.get() != Thread.currentThread()) throw new IllegalMonitorStateException();
        // 获取 独占锁 状态 0 未被占用
        int wct = writeCount.get();

        // 计算 独占锁剩余占用,即释放了锁之后新的 writeCount 应该是多少
        int nwct = wct - releases;
        // 不管是否完全释放,都更新count值
        writeCount.set(nwct);
        // 如果完全释放,则将owner设置为null
        if (nwct == 0) {
            owner.compareAndSet(Thread.currentThread(), null);
            return true;
        } else {
            return false;
        }
    }


    /************************* 共享锁 *****************************/

    // 获取共享锁
    public void lockShared() {
        int arg = 1;

        if (!tryLockShared(arg)) {    //如果tryAcquireShare失败
            // 创建锁节点,并且标记为共享锁
            WaitNode node = new WaitNode(Thread.currentThread(), 1, arg);
            // 将当前进程放入队列
            waiters.offer(node);  //加入队列

            while (true) {
                //若队列头部的元素是当前线程
                WaitNode head = waiters.peek();
                if (head != null && head.thread == Thread.currentThread()) {
                    //尝试获取共享锁,若成功
                    if (tryLockShared(arg)) {
                        //将当前线程从队列中移除
                        waiters.poll();
                        WaitNode next = waiters.peek();
                        if (next != null && next.type == 1) {   // 如果下一个线程也是等待共享锁
                            LockSupport.unpark(next.thread);    // 将其唤醒
                        }
                        return;     // 退出方法
                    } else {  // 若尝试失败
                        LockSupport.park();     // 挂起线程
                    }
                } else {  // 若不是头部元素
                    LockSupport.park();
                }
            }
        }
    }

    // 释放共享锁
    public boolean unLockShared() {
        int arg = 1;
        if (tryUnLockShared(arg)) {     //当read count变为0,才叫release share成功
            WaitNode next = waiters.peek();
            if (next != null) {
                LockSupport.unpark(next.thread);
            }
            return true;
        }
        return false;
    }

    // 尝试获取共享锁
    public boolean tryLockShared(int acquires) {
        while (true) {
            // 如果已经被独占锁占用,且当前独占锁所属的线程不是当前线程,加锁失败
            if (writeCount.get() != 0 && owner.get() != Thread.currentThread()) {
                return false;
            }
            // 获取共享锁状态
            int rct = readCount.get();
            // 加锁,修改状态,并且返回值
            if (readCount.compareAndSet(rct, rct + acquires)) {
                return true;
            }
        }
    }

    //尝试解锁共享锁
    public boolean tryUnLockShared(int releases) {
        while (true) {
            int rct = readCount.get();
            int nrct = rct - releases;
            if (readCount.compareAndSet(rct, nrct)) {
                return nrct == 0;
            }
        }
    }


    class WaitNode {
        int type = 0;   //0 为想获取独占锁的线程,  1为想获取共享锁的线程
        Thread thread = null;
        int arg = 0;

        public WaitNode(Thread thread, int type, int arg) {
            this.thread = thread;
            this.type = type;
            this.arg = arg;
        }
    }
}
  • 测试代码
package lock.readwritelock1;

/**
 * @Author: Neco
 * @Description:
 * @Date: create in 2022/6/20 22:02
 */
public class ReadWriteLockDemo {

    static NecoReadWriteLock rwLock = new NecoReadWriteLock();

    static volatile int i = 0;

    static void add() {
        i++;
    }

    public static void main(String args[]) throws InterruptedException {
        long startTime = System.currentTimeMillis();


        for (int a = 1; a <= 20000; a++) {
            final int n = a;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if (n % 5 == 0) {
                        rwLock.lock();
                        add();
                        rwLock.unlock();
                    } else {
                        rwLock.lockShared();
                        System.out.println("i=" + i);
                        int a = i;
                        rwLock.unLockShared();
                    }
                }
            }).start();
        }


        while (true) {
            System.out.println("目前耗时:" + (System.currentTimeMillis() - startTime) / 1000 + "s");
            Thread.sleep(1000L);
            System.out.println("i=" + i);

        }
    }
}
  • 运行结果,截选
i=3996
i=3996
i=3996
i=3997
i=3997
i=3997
i=3997
i=3998
i=3998
i=3998
i=3998
i=3999
i=3999
目前耗时:1s
i=3999
i=3999
i=4000
目前耗时:2s
i=4000
目前耗时:3s

当然以上的代码还有很多可以修改的内容,比如抽取公共的方法,又比如实现公平锁和非公平锁的相关内容,具体的实现可以根据需求来继续扩充,以上的代码只是作为一个参考。

  • 抽取重复代码块
package lock.readwritelock2;

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

public class CommonMask { //重复的代码提取到这里

    volatile AtomicInteger readCount = new AtomicInteger(0);
    AtomicInteger writeCount = new AtomicInteger(0);

    //独占锁 拥有者
    AtomicReference owner = new AtomicReference<>();

    //等待队列
    public volatile LinkedBlockingQueue waiters = new LinkedBlockingQueue();

    class WaitNode {
        int type = 0;   //0 为想获取独占锁的线程,  1为想获取共享锁的线程
        Thread thread = null;
        int arg = 0;

        public WaitNode(Thread thread, int type, int arg) {
            this.thread = thread;
            this.type = type;
            this.arg = arg;
        }
    }

    //获取独占锁
    public void lock() {
        int arg = 1;
        //尝试获取独占锁,若成功,退出方法,    若失败...
        if (!tryLock(arg)) {
            //标记为独占锁
            WaitNode waitNode = new WaitNode(Thread.currentThread(), 0, arg);
            waiters.offer(waitNode);    //进入等待队列

            //循环尝试拿锁
            for (; ; ) {
                //若队列头部是当前线程
                WaitNode head = waiters.peek();
                if (head != null && head.thread == Thread.currentThread()) {
                    if (!tryLock(arg)) {      //再次尝试获取 独占锁
                        LockSupport.park();     //若失败,挂起线程
                    } else {     //若成功获取
                        waiters.poll();     //  将当前线程从队列头部移除
                        return;         //并退出方法
                    }
                } else {  //若不是队列头部元素
                    LockSupport.park();     //将当前线程挂起
                }
            }
        }
    }

    //释放独占锁
    public boolean unlock() {
        int arg = 1;

        //尝试释放独占锁 若失败返回true,若失败...
        if (tryUnlock(arg)) {
            WaitNode next = waiters.peek(); //取出队列头部的元素
            if (next != null) {
                Thread th = next.thread;
                LockSupport.unpark(th);     //唤醒队列头部的线程
            }
            return true;                //返回true
        }
        return false;
    }

    //尝试获取独占锁
    public boolean tryLock(int acquires) {
        //如果read count !=0 返回false
        if (readCount.get() != 0)
            return false;

        int wct = writeCount.get();     //拿到 独占锁 当前状态

        if (wct == 0) {
            if (writeCount.compareAndSet(wct, wct + acquires)) {     //通过修改state来抢锁
                owner.set(Thread.currentThread());  //  抢到锁后,直接修改owner为当前线程
                return true;
            }
        } else if (owner.get() == Thread.currentThread()) {
            writeCount.set(wct + acquires);     //修改count值
            return true;
        }

        return false;
    }

    //尝试释放独占锁
    public boolean tryUnlock(int releases) {
        //若当前线程没有 持有独占锁
        if (owner.get() != Thread.currentThread()) {
            throw new IllegalMonitorStateException();       //抛IllegalMonitorStateException
        }

        int wc = writeCount.get();
        int nextc = wc - releases;      //计算 独占锁剩余占用
        writeCount.set(nextc);      //不管是否完全释放,都更新count值

        if (nextc == 0) {  //是否完全释放
            owner.compareAndSet(Thread.currentThread(), null);
            return true;
        } else {
            return false;
        }

    }


    //获取共享锁
    public void lockShared() {
        int arg = 1;

        if (tryLockShared(arg) < 0) {    //如果tryAcquireShare失败
            //将当前进程放入队列
            WaitNode node = new WaitNode(Thread.currentThread(), 1, arg);
            waiters.offer(node);  //加入队列

            for (; ; ) {
                //若队列头部的元素是当前线程
                WaitNode head = waiters.peek();
                if (head != null && head.thread == Thread.currentThread()) {
                    if (tryLockShared(arg) >= 0) {    //尝试获取共享锁,  若成功
                        waiters.poll();      //将当前线程从队列中移除

                        WaitNode next = waiters.peek();
                        if (next != null && next.type == 1) {    //如果下一个线程也是等待共享锁
                            LockSupport.unpark(next.thread);    //将其唤醒
                        }
                        return;     //退出方法
                    } else {                      //若尝试失败
                        LockSupport.park();     //挂起线程
                    }
                } else {  //若不是头部元素
                    LockSupport.park();
                }

            }
        }
    }

    //解锁共享锁
    public boolean unLockShared() {
        int arg = 1;

        if (tryUnLockShared(arg)) {     //当read count变为0,才叫release share成功
            WaitNode next = waiters.peek();
            if (next != null) {
                LockSupport.unpark(next.thread);
            }
            return true;
        }
        return false;
    }

    //尝试获取共享锁
    public int tryLockShared(int acquires) {
        for (; ; ) {
            if (writeCount.get() != 0 &&
                    owner.get() != Thread.currentThread())
                return -1;

            int rct = readCount.get();
            if (readCount.compareAndSet(rct, rct + acquires)) {
                return 1;
            }
        }
    }

    //尝试解锁共享锁
    public boolean tryUnLockShared(int releases) {
        for (; ; ) {
            int rc = readCount.get();
            int nextc = rc - releases;
            if (readCount.compareAndSet(rc, nextc)) {
                return nextc == 0;
            }
        }
    }
}

AQS (AbstractQueuedSynchronizer)

实际上,AQS实现对我们同步状态的管理,以及阻塞线程进行排队等待通知的一系列底层实现的处理,AQS核心其实就包含了以下几个内容:

  • 同步队列
  • 锁的释放和获取,包含了独占锁和共享锁一系列的实现
  • 本质来说,跟上方的重复代码块的内容(模板方法)类似
  • AQS中是使用链表来实现等待队列的

AQS为一系列同步器依赖于一个单独的原子变量(state)的同步器提供了一个非常有用的基础。子类们必须定义改变state变量的protected方法,这些方法定义了state是如何被获取或释放的。鉴于此,本类中的其他方法执行所有的排队和阻塞机制。子类也可以维护其他的state变量,但是为了保证同步,必须原子地操作这些变量。

AQS 抽象队列同步器
AQS 抽象队列同步器
  • 提供了对资源占用、释放,线程的挂起、唤醒的逻辑。
  • 预留了各种 try方法给用户实现
  • 可以用在各种需要控制资源争用的场景中。(ReentrantLock/CountDownLatch/Semphore)

此外 ReadWriteLock 使用了一个int 存储了两个count的值,以解决readCount和writeCount的在读写线程同时操作时可能冲突的问题



如果觉得有收获就点个赞吧,更多知识,请点击关注查看我的主页信息哦~

你可能感兴趣的:(Java并发 - 读写锁与AQS简单了解)