007. J.U.C 之锁的使用

£可爱£侵袭症+ 提交于 2020-04-06 01:53:40

1. Lock API


1. Locks 包类层次结构

img

2. Lock 接口

方法签名 描述
void lock(); 获取锁(不死不休)
boolean tryLock(); 获取锁(浅尝辄止)
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 获取锁(过时不候)
void lockInterruptibly() throws InterruptedException; 获取锁(任人摆布)
void unlock(); 释放锁
Condition newCondition();
  • 结论:

    • lock() 最常用,并且线程不会被中断;
    • lockInterruptibly() 方法一般更昂贵,有的 impl 可能没有实现 lockInterruptibly(),只有真的需要效应中断时,才使用,使用之前看看 impl 对该方法的描述。
public class Demo1_GetLock {

    // 公平锁
//    static Lock lock = new ReentrantLock(true);

    // 非公平锁
    static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        // 主线程 拿到锁
        lock.lock();

        Thread th = new Thread(() -> {
            /*
            // 子线程 获取锁(浅尝辄止)
            boolean result = lock.tryLock();
            System.out.println("是否获取到锁: " + result);
             */

            /*
            // 子线程 获取锁(过时不候)
            try {
                System.out.println("开始获取锁");
                boolean result = lock.tryLock(5, TimeUnit.SECONDS);
                System.out.println("是否获取到锁: " + result);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
             */

            /*
            // 子线程 获取锁(任人摆布)
            try {
                System.out.println("开始获取锁 Interruptibly");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("父线程让我停止了。");
            }
             */

            // 子线程 获取锁(不死不休)
            System.out.println("开始获取锁");
            lock.lock();
            System.out.println("成功获取到锁");
        });
        th.start();

        Thread.sleep(7000);
        th.interrupt();
        System.out.println("th interrupted ...");

        Thread.sleep(15000);
        lock.unlock();
    }

}

3. Condition

  • Object 中的 wait()、notify()、notifyAll() 只能和 synchronized 配合使用,可以唤醒一个或者全部(单个等待集);
  • Condition 需要与 Lock 配合使用,提供多个等待集合,更精确地控制。
协作方式 死锁方式1 死锁方式2(先唤醒,再挂起) 备注
suspend/resume 死锁 死锁 弃用
wait/notify 不死锁 死锁 只用于 synchronized 关键字
park/unpark 死锁 不死锁
condition 不死锁 死锁
  • 模拟 condition 死锁

    public class Demo3_Condition {
        private static Lock lock = new ReentrantLock();
        private static Condition condition = lock.newCondition();
    
        public static void main(String[] args) throws InterruptedException {
            Thread th = new Thread(() -> {
                try {
                    // 模拟先调用 signal,再调用 await,会进入死锁状态
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.lock();
    
                try {
                    System.out.println("here I am ... 1");
                    condition.await();
                    System.out.println("here I am ... 2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            });
            th.start();
    
            Thread.sleep(5000);
    
            lock.lock();
            condition.signal();
            lock.unlock();
        }
    }
    
  • 经典示例: 通过 condition 实现阻塞队列

    public class Demo4_Condition3 {
        public static void main(String[] args) throws InterruptedException {
            MyBlockingQueue bb = new MyBlockingQueue(5);
    
            new Thread(() -> {
                for (int i = 0; i < 20; i ++) {
                    bb.put("x" + i);
                }
            }).start();
    
            Thread.sleep(3000);
            System.out.println("开始从队列中取元素...");
            for (int i = 0; i < 10; i ++) {
                bb.take();
                Thread.sleep(3000);
            }
        }
    }
    
    /**
     * 自己实现一个阻塞队列,只能存储 n 个元素
     * put 时,若队列未满,直接 put
     *           若队列已满,就阻塞,直到再有空间
     * get 时,若队列中有元素,获取到元素
     *           若无元素,则等待元素
     */
    
    class MyBlockingQueue {
        List<Object> list = new ArrayList<>();
        private int length;
    
        private Lock lock = new ReentrantLock();
        private Condition putCondition = lock.newCondition();
        private Condition takeCondition = lock.newCondition();
    
        public MyBlockingQueue(int length) {
            this.length = length;
        }
    
        public void put(Object obj) {
            lock.lock();
            try {
                while (true) {
                    if (list.size() < this.length) {
                        list.add(obj);
                        takeCondition.signal();
                        System.out.println("put: " + obj);
                        return;
                    } else {
                        putCondition.await();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public Object take() {
            lock.lock();
            try {
                while (true) {
                    if (list.size() > 0) {
                        Object obj = list.remove(0);
                        putCondition.signal();
                        System.out.println("tack: " + obj);
                        return obj;
                    } else {
                        takeCondition.await();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                return null;
            }
        }
    }
    

4. ReentrantLock

public class Demo5_Reentrant {
    static Lock lock = new ReentrantLock(); // 可重入锁

    public static void main(String[] args) {
        System.out.println("get lock 1 ...");
        lock.lock();

        System.out.println("get lock 2 ...");
        lock.lock();

        // 打印消息,说明再次拿到锁
        System.out.println("here I am ...");

        System.out.println("unlock 1 ...");
        lock.unlock();

        System.out.println("unlock 2 ...");
        lock.unlock();

        System.out.println("unlock 3 ...");
        lock.unlock(); // 第三次释放锁,报错
    }
}

img

  • 自己实现一个 ReentrantLock
public class MyReentrantLock implements Lock {
    // 锁拥有者
    AtomicReference<Thread> owner = new AtomicReference<>();

    // 记录冲入次数的 count
    AtomicInteger count = new AtomicInteger(0);

    // 等待队列
    private LinkedBlockingDeque<Thread> waiters = new LinkedBlockingDeque<>();

    @Override
    public boolean tryLock() {
        int ct = count.get();
        // 判断 count 是否为 0,若 count!=0,说明锁被占用
        if (ct != 0) {
            if (Thread.currentThread() == owner.get()) {
                // 判断是否是当前线程占用,如果是当前线程占用,做重入
                count.set(ct + 1);
                return true;
            }
        } else {
            // 若 count=0,说明锁未被占用,通过 CAS 来抢锁
            if (count.compareAndSet(ct, ct + 1)) {
                owner.set(Thread.currentThread());
                return true;
            }
        }
        return false;
    }

    @Override
    public void lock() {
        if (! tryLock()) {
            // 没有拿到锁,加入等待队列
            waiters.offer(Thread.currentThread());

            LockSupport.park();

            while (true) {
                // 若线程是队列头部,尝试加锁
                Thread head = waiters.peek();
                if (head == Thread.currentThread()) {
                    if (!tryLock()) {
                        LockSupport.park();
                    } else {
                        waiters.poll();
                        return;
                    }
                } else {
                    // 线程挂起
                    LockSupport.park();
                }
            }
        }
    }

    public boolean tryUnlock() {
        if (owner.get() != Thread.currentThread()) {
            throw new IllegalMonitorStateException();
        } else {
            // unlock 是将 count-1
            int ct = count.get();
            int nextC = ct - 1;
            count.set(nextC);

            // 判断 count 是否为 0,如果为 0,就释放锁成功,否则释放失败
            if (nextC == 0) {
                owner.compareAndSet(Thread.currentThread(), null);
                return true;
            } else {
                return false;
            }
        }
    }

    @Override
    public void unlock() {
        if (tryUnlock()) {
            Thread head = waiters.peek();
            if (head != null) {
                LockSupport.unpark(head);
            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }


    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

5. synchronized vs Lock

  • Synchronized

    • 优点
      • 使用简单,语义清晰,哪里需要点哪里。
      • 由 JVM 提供,提供来多种优化方案(锁粗化、锁消除、偏向锁、轻量级锁)。
      • 锁的释放由虚拟机来完成,不用人工干预,也降低了死锁的可能性。
    • 缺点
      • 无法实现一些锁的高级功能如: 公平锁、中断锁、超时锁、读写锁、共享锁等。
  • Lock

    • 优点
      • 所有 synchronized 的缺点。
      • 可以实现更过的功能,让 synchronized 缺点更多。
    • 缺点
      • 需手动释放锁 unlock,新手使用不当可能造成死锁。

2. 读写锁: ReadWriteLock


1. 概念

  • 维护一对关联锁,一个只用于读操作,一个只用于写操作。
  • 读锁可以由多个线程同时持有,写锁是排他的,同一时间,两把锁不能被不同线程持有。

2. Lock 在读多写少的情况下的缺点演示

public class Demo6_ReadWrite01 {
    volatile long i = 0;

    Lock lock = new ReentrantLock();

    public void read() {
        lock.lock();
        long a = i;
        lock.unlock();
    }

    public void write() {
        lock.lock();
        i ++;
        lock.unlock();
    }

    public static void main(String[] args) {
        final Demo6_ReadWrite01 demo = new Demo6_ReadWrite01();

        for (int i = 0; i <= 30; i ++) {
            int n = i;
            new Thread(() -> {
                long startTime = System.currentTimeMillis();
                for (int j = 0; j < 400000; j ++) {
                    if (n == 0) demo.write();
                    else demo.read();
                }
            }).start();
        }

        long endTime = System.currentTimeMillis();
    }
}
  • 此时,只有第一次的时候是写操作,其他都是读操作。使用 Lock,每次只能有一个线程做读取操作,效率会非常低。

  • 如下是 ReadWriteLock 的演示

public class Demo6_ReadWrite02 {
    volatile long i = 0;

    ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void read() {
        rwLock.readLock().lock();
        long a = i;
        rwLock.readLock().unlock();
    }

    public void write() {
        rwLock.writeLock().lock();
        i ++;
        rwLock.writeLock().unlock();
    }

    public static void main(String[] args) {
        final Demo6_ReadWrite02 demo = new Demo6_ReadWrite02();

        for (int i = 0; i <= 30; i ++) {
            int n = i;
            new Thread(() -> {
                long startTime = System.currentTimeMillis();
                for (int j = 0; j < 400000; j ++) {
                    if (n == 0) demo.write();
                    else demo.read();
                }
            }).start();
        }

        long endTime = System.currentTimeMillis();
    }
}

3. 适用场景

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

  • 将 HashMap 改造成线程安全的

// 将 hashmap 改造成一个并发安全的
// 这是 ReentrantReadWriteLock 注释中给出的一个示例
public class Demo7_Map {
    private final Map<String, Object> m = new HashMap<>();

    private final ReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public Object get(String key) {
        r.lock();
        try {
            return m.get(key);
        } finally {
            r.unlock();
        }
    }

    public Object put(String key, Object value) {
        w.lock();
        try {
            return m.put(key, value);
        } finally {
            w.unlock();
        }
    }

    public void clear() {
        w.lock();
        try {
            m.clear();
        } finally {
            w.unlock();
        }
    }

}

4. 锁降级

  • 指的是写锁降级为读锁。持有写锁的同时,再获取读锁,随后释放写锁的过程。
  • 写锁是线程独占,读锁是共享,所以写->读是降级。(读->写是不能实现的)
public class Demo8_CacheData {
    public static void main(String[] args) {

    }
}

class TeacherInfoCache {
    static volatile boolean cacheValid;
    static final ReadWriteLock rwl = new ReentrantReadWriteLock();

    /**
     * 实现一个使用 Cache 数据的方法
     * 如果缓存可用,就直接获取数据
     * 如果缓存不可用,从 DB 加载数据到缓存
     * 拿到数据之后,需要使用数据,这期间,数据不能发生变化
     */
    static void processCacheData(String dataKey) {
        Object data = null;
        rwl.readLock().lock();
        try {
            if (cacheValid) {
                data = Redis.data.get(dataKey);
            } else {
                // 到数据库查询数据
//                DataBase.queryUserInfo(); // 存在缓存雪崩的问题
                rwl.readLock().lock();
                rwl.writeLock().lock();
                try {
                    if (! cacheValid) {
                        data = DataBase.queryUserInfo();
                        Redis.data.put(dataKey, data);
                        cacheValid = true;
                    } else {
                        data = Redis.data.get(dataKey);
                    }
                } finally {
                    // 锁降级 线程在占有写锁的时候,获取读锁,同时拥有读写锁
                    rwl.readLock().lock();
                    rwl.writeLock().unlock();
                }
            }
        } finally {
            rwl.readLock().unlock();
        }
    }
}

class DataBase {
    static String queryUserInfo() {
        System.out.println("查询数据库...");
        return "name:Kody,age:40,gender:true";
    }
}

class Redis {
    static Map<String, Object> data = new HashMap<>();
}

5. 读写锁的内部原理

img

3. 读写锁实现


1. 自己实现一个重入锁和读写锁

  • 公共代码
public class MyAQS {

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

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

    // 等待队列
    public volatile LinkedBlockingQueue<WaitNode> waiters = new LinkedBlockingQueue<>();
    public class WaitNode {
        int type = 0; // 0 为想获取独占锁的线程,1 为想获取共享所的线程
        Thread thread =  null;
        int arg = 0;

        public WaitNode(Thread thread, int type, int age) {
            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); // 进入等待队列

            // 循环尝试拿锁
            while (true) {
                // 若队列头部是当前线程
                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 head = waiters.peek();
            if (head != null) {
                Thread th = head.thread;
                LockSupport.unpark(th); // 唤醒队列头部的线程
            }
            return true; // 返回 true
        }
        return false;
    }

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

        if (tryLockShared(arg) < 0) { // 如果 tryLockShared 失败
            // 将当前线程放入队列
            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) >= 0) { // 尝试获取共享锁,成功
                        waiters.poll(); // 将当前线程从队列中移除

                        WaitNode next = waiters.peek();
                        if (next != null && next.type == 1) { // 如果下一个线程也是等待共享锁
                            LockSupport.unpark(next.thread); // 将其唤醒
                        }
                        return; // 退出方法
                    }
                } 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 tryLock(int acquires) {
        throw new UnsupportedOperationException();
    }

    // 尝试释放独占锁
    public boolean tryUnlock(int releases) {
        throw new UnsupportedOperationException();
    }

    // 尝试获取共享锁
    public int tryLockShared(int acquires) {
        throw new UnsupportedOperationException();
    }

    // 尝试解锁共享锁
    public boolean tryUnLockShared(int releases) {
        throw new UnsupportedOperationException();
    }

}
  • 重入锁实现
public class MyReentrantLock implements Lock {

    private boolean isFair;

    public MyReentrantLock(boolean isFair) {
        this.isFair = isFair;
    }

    MyAQS common = new MyAQS() {
        public boolean tryLock(int acquires) {
            if (isFair) {
                return tryFairLock(acquires);
            } else {
                return tryNotFairLock(acquires);
            }
        }

        // 非公平锁
        public boolean tryNotFairLock(int acquires) {
            // 如果 read count != 0,返回 false
            if (readCount.get() != 0) {
                return false;
            }

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

            if (wct == 0) {
                if (writeCount.compareAndSet(wct, wct + 1)) { // 通过修改 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 tryFairLock(int acquires) {
            // 如果 read count != 0,返回 false
            if (readCount.get() != 0) {
                return false;
            }

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

            if (wct == 0) {
                WaitNode head = waiters.peek();
                if (head != null && head.thread == Thread.currentThread() &&
                        writeCount.compareAndSet(wct, wct + 1)) { // 通过修改 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();
            }

            int wc = writeCount.get();
            int nextc = wc - releases; // 计算 独占锁剩余占用
            writeCount.set(nextc);

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

    public void lock() {
        common.lock();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    public boolean tryLock() {
        return common.tryLock(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {
        common.unlock();
    }

    @Override
    public Condition newCondition() {
        return null;
    }

}
  • 读写锁实现
public class MyReadWriteLock implements ReadWriteLock {
    MyAQS common = new MyAQS() {
        // 尝试获取独占锁
        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 + 1)) { // 通过修改 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();
            }

            int wc = writeCount.get();
            int nextc = wc - releases; // 计算 独占锁剩余占用
            writeCount.set(nextc);

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

        // 尝试获取共享锁
        public int tryLockShared(int acquires) {
            while (true) {
                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) {
            while (true) {
                int rc = readCount.get();
                int nextc = rc - releases;
                if (readCount.compareAndSet(rc, nextc)) {
                    return nextc == 0;
                }
            }
        }
    };

    @Override
    public Lock readLock() {
        return new Lock() {
            @Override
            public void lock() {
                common.lock();
            }

            @Override
            public boolean tryLock() {
                return common.tryLock(1);
            }

            @Override
            public void unlock() {
                common.unlock();
            }

            @Override
            public void lockInterruptibly() throws InterruptedException {

            }

            @Override
            public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
                return false;
            }

            @Override
            public Condition newCondition() {
                return null;
            }
        };
    }

    @Override
    public Lock writeLock() {
        return new Lock() {
            @Override
            public void lock() {
                common.lockShared();
            }

            @Override
            public boolean tryLock() {
                return common.tryLockShared(1) == 1;
            }

            @Override
            public void unlock() {
                common.unLockShared();
            }

            @Override
            public void lockInterruptibly() throws InterruptedException {

            }

            @Override
            public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
                return false;
            }

            @Override
            public Condition newCondition() {
                return null;
            }
        };
    }
}
  • 重入锁测试
public class Test_ReentrantLock {

    volatile static int i = 0;

    static MyReentrantLock lc = new MyReentrantLock(true);

    public static void add() {
        lc.lock();
        i ++;
        lc.unlock();
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i ++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j ++) {
                    add();
                }
                System.out.println("done...");
            }).start();
        }

        Thread.sleep(6000);
        System.out.println(i);
    }

}
  • 读写锁测试
public class Test_ReadWriteLock {

    static MyReadWriteLock rwLock = new MyReadWriteLock();

    static volatile int i = 0;

    static void add() {
        i ++;
    }

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

        for (int a = 0; a < 20000; a ++) {
            final int n = a;
            new Thread(() -> {
                if (n % 5 == 0) {
                    rwLock.writeLock().lock();
                    add();
                    rwLock.writeLock().unlock();
                } else {
                    rwLock.readLock().lock();
                    System.out.println("i = " + i);
                    int b = i;
                    rwLock.readLock().unlock();
                }
            }).start();
        }

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

}

2. AQS(AbstractQueuedSynchronizer) 抽象队列同步器

  • java.util.concurrent.locks.AbstractQueuedSynchronizer

img

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

3. ReadWriteLock 用一个 int 存储了两个 count 值

img

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!