• 深入理解独占锁ReentrantLock类锁


     ReentrantLock介绍

    【1】ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。

    【2】相对于 synchronized, ReentrantLock具备如下特点:

    1)可中断 
    2)可以设置超时时间
    3)可以设置为公平锁 
    4)支持多个条件变量
    5)与 synchronized 一样,都支持可重入

     

    ReentrantLock问题分析

    【1】ReentrantLock公平锁和非公平锁的性能谁更高?

      1)那肯定是非公平锁,但是为什么是非公平更高呢?

      2)因为涉及到了线程的park()与unpark()操作,不管是ReentrantLock还是synchronized,都在避免这些操作。

        (1)如ReentrantLock的非公平同步器在得不到锁的情况下,即将要进入之前会再加一次锁,生成节点之后又会加一次锁,把节点放入队列之后又会加一次锁,最终迫不得已才会进行park()操作。

        (2)如synchronized,在生成monitor的过程之中也会多次尝试加锁,避免monitor的生成。

      3)为什么要避免呢?这就涉及到线程的概念了。

        (1)因为park()与unpark()操作涉及到了线程的上下文切换,同时又涉及到了时间片轮转机制。

        (2)线程上下文切换,需要将旧的线程资源保存回内存【保存执行到了哪一步,需要什么东西】,将新的线程的资源加载入CPU,让新线程具备执行的资源并开始执行。但是这些操作都是需要花费时间的,会消耗一部分时间片的资源。如(这里仅仅只是举例说明),一个时间片本来就是50s,你拿到的时候花费了一定的时间(如10s)进行上下文切换,现在刚执行不到5s,你又要进行一次切换(又要花费10s)。那下一个拿到时间片的线程会不会还是会继续切换呢?而且你要下次运行就又要等时间片了。

        (3)所以说,本质上非公平机制是为了让持有CPU的线程尽可能多的做有用的任务,减少上线下文切换带来的开销,毕竟时间片来之不易,本身就是从众多线程之中好不容易分配得来的。

     

    ReentrantLock的使用

    【1】使用模板

    复制代码
    Lock lock = new ReentrantLock();
    //加锁
    lock.lock();
    try {
        // 临界区代码
        // TODO 业务逻辑:读写操作不能保证线程安全
    } finally {
        // 解锁,放置在这里的原因是保证异常情况都不能干扰到解锁逻辑
        lock.unlock();
    }
    复制代码

     

    【2】可重入的尝试

    复制代码
    public class ReentrantLockDemo {
        public static ReentrantLock lock = new ReentrantLock();
    
        public static void main(String[] args) {
            method1();
        }
        public static void method1() {
            lock.lock();
            try {
                log.debug("execute method1");
                method2();
            } finally {
                lock.unlock();
            }
        }
        public static void method2() {
            lock.lock();
            try {
                log.debug("execute method2");
                method3();
            } finally {
                lock.unlock();
            }
        }
        public static void method3() {
            lock.lock();
            try {
                log.debug("execute method3");
            } finally {
                lock.unlock();
            }
        }
    }
    复制代码

     

    【3】中断机制尝试

      进行说明:这里面其实是main线程先获得了锁,所以t1线程其实是先进入队列里面,然后在main线程里面将t1设置为了中断。当main线程释放锁的时候,t1去加锁,发现自己被中断了,所以抛出中断异常,退出加锁。【其实这个中断加锁,怎么说,就是可以让失去加锁的权利,但是不影响你去排队】

    复制代码
    @Slf4j
    public class ReentrantLockDemo {
    
        public static void main(String[] args) throws InterruptedException {
            ReentrantLock lock = new ReentrantLock();
    
            Thread t1 = new Thread(() -> {
    
                log.debug("t1启动...");
    
                try {
                    lock.lockInterruptibly();
                    try {
                        log.debug("t1获得了锁");
                    } finally {
                        lock.unlock();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    log.debug("t1等锁的过程中被中断");
                }
    
            }, "t1");
            
            lock.lock();
            try {
                log.debug("main线程获得了锁");
                t1.start();
                //先让线程t1执行
                Thread.sleep(10000);
    
                t1.interrupt();
                log.debug("线程t1执行中断");
            } finally {
                lock.unlock();
            }
    
        }
    
    }
    复制代码

     

    【4】锁超时尝试

    复制代码
    @Slf4j
    public class ReentrantLockDemo {
    
        public static void main(String[] args) throws InterruptedException {
            ReentrantLock lock = new ReentrantLock();
    
            Thread t1 = new Thread(() -> {
    
                log.debug("t1启动...");
                //超时
                try {
                    // 注意: 即使是设置的公平锁,此方法也会立即返回获取锁成功或失败,公平策略不生效
                    if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                        log.debug("等待 1s 后获取锁失败,返回");
                        return;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return;
                }
    
                try {
                    log.debug("t1获得了锁");
                } finally {
                    lock.unlock();
                }
    
            }, "t1");
    
    
            lock.lock();
            try {
                log.debug("main线程获得了锁");
                t1.start();
                //先让线程t1执行
                Thread.sleep(2000);
            } finally {
                lock.unlock();
            }
    
        }
    
    }
    复制代码

     

    【5】条件变量的尝试

      说明:

        1)java.util.concurrent类库中提供Condition类来实现线程之间的协调。调用Condition.await() 方法使线程等待,其他线程调用Condition.signal() 或 Condition.signalAll() 方法唤醒等待的线程。

        2)由于可控的原因我们甚至可以多个条件队列来进行对线程调控。

      注意:调用Condition的await()和signal()方法,都必须在lock保护之内。

    复制代码
    @Slf4j
    public class ReentrantLockDemo {
        private static ReentrantLock lock = new ReentrantLock();
        private static Condition cigCon = lock.newCondition();
        private static Condition takeCon = lock.newCondition();
    
        private static boolean hashcig = false;
        private static boolean hastakeout = false;
    
        //送烟
        public void cigratee(){
            lock.lock();
            try {
                while(!hashcig){
                    try {
                        log.debug("没有烟,歇一会");
                        cigCon.await();
    
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
                log.debug("有烟了,干活");
            }finally {
                lock.unlock();
            }
        }
    
        //送外卖
        public void takeout(){
            lock.lock();
            try {
                while(!hastakeout){
                    try {
                        log.debug("没有饭,歇一会");
                        takeCon.await();
    
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
                log.debug("有饭了,干活");
            }finally {
                lock.unlock();
            }
        }
    
        public static void main(String[] args) {
            ReentrantLockDemo6 test = new ReentrantLockDemo6();
            new Thread(() ->{
                test.cigratee();
            }).start();
    
            new Thread(() -> {
                test.takeout();
            }).start();
    
            new Thread(() ->{
                lock.lock();
                try {
                    hashcig = true;
                    log.debug("唤醒送烟的等待线程");
                    cigCon.signal();
                }finally {
                    lock.unlock();
                }
    
    
            },"t1").start();
    
            new Thread(() ->{
                lock.lock();
                try {
                    hastakeout = true;
                    log.debug("唤醒送饭的等待线程");
                    takeCon.signal();
                }finally {
                    lock.unlock();
                }
    
    
            },"t2").start();
        }
    
    }
    复制代码

     

     

    ReentrantLock源码分析(版本为jdk14)

    【0】前置部分最好有关于JDK实现管程的了解【可查看  深入理解AQS--jdk层面管程实现】

    【1】ReentrantLock类自身部分

      0)继承关系

    复制代码
    //锁的接口定义,定义了一个锁该具备哪一些功能
    public interface Lock {
    
        void lock();
    
        void lockInterruptibly() throws InterruptedException;
    
        boolean tryLock();
    
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
        void unlock();
    
        Condition newCondition();
    }
    复制代码

      1)属性值

    //同步器句柄,同步器提供了所有的实现机制
    private final Sync sync;

      2)构造方法

    复制代码
    //默认是采用非公平的同步器
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    
    //此外可以根据传入的参数选择同步器
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    复制代码

      3)其他方法【看完之后,你会发现,其实ReentrantLock什么事都不干,统统都交给了持有的AQS同步器去干活了,有一种修饰器设计模式的味道,只是包装了一下,具体内部的同步器类型由自己选择,所以同步器显得就很重要】

    复制代码
    public class ReentrantLock implements Lock, java.io.Serializable {
        .....
        //获取锁定
        public void lock() {
            sync.lock();
        }
    
        //中断式的加锁,如果没有被中断就会加锁
        public void lockInterruptibly() throws InterruptedException {
            sync.lockInterruptibly();
        }
    
        //仅当在调用时锁不被另一个线程持有时才获取锁
        public boolean tryLock() {
            return sync.tryLock();
        }
       //超时加锁,限定加锁时间
        public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
            return sync.tryLockNanos(unit.toNanos(timeout));
        }
    
        //尝试释放此锁
        public void unlock() {
            sync.release(1);
        }
    
        //返回一个用于此锁定实例的条件实例,说白了就是监视器
        public Condition newCondition() {
            return sync.newCondition();
        }
    
        //查询当前线程在此锁上保留的数量
        public int getHoldCount() {
            return sync.getHoldCount();
        }
    
        public boolean isHeldByCurrentThread() {
            return sync.isHeldExclusively();
        }
    
        //判断同步器是否在被持有状态,也就是被加锁了
        public boolean isLocked() {
            return sync.isLocked();
        }
    
        //判断同步器的类型
        public final boolean isFair() {
            return sync instanceof FairSync;
        }
    
        protected Thread getOwner() {
            return sync.getOwner();
        }
    
        public final boolean hasQueuedThreads() {
            return sync.hasQueuedThreads();
        }
    
        //判断同步器里面是否有该线程
        public final boolean hasQueuedThread(Thread thread) {
            return sync.isQueued(thread);
        }
    
        public final int getQueueLength() {
            return sync.getQueueLength();
        }
    
        protected Collection getQueuedThreads() {
            return sync.getQueuedThreads();
        }
    
        public boolean hasWaiters(Condition condition) {
            if (condition == null)
                throw new NullPointerException();
            if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
                throw new IllegalArgumentException("not owner");
            return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
        }
    
        //返回条件队列里面等待的线程的个数
        public int getWaitQueueLength(Condition condition) {
            if (condition == null)
                throw new NullPointerException();
            if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
                throw new IllegalArgumentException("not owner");
            return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
        }
    
        //返回条件队列里面等待的线程
        protected Collection getWaitingThreads(Condition condition) {
            if (condition == null)
                throw new NullPointerException();
            if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
                throw new IllegalArgumentException("not owner");
            return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
        }
    
    
    }
    复制代码

     

    【2】抽象的Sync类部分

    复制代码
    abstract static class Sync extends AbstractQueuedSynchronizer {
        //定义了核心的加锁逻辑
        @ReservedStackAccess
        final boolean tryLock() {
            Thread current = Thread.currentThread();
            //获取State属性值,这是在AQS里面定义的值,用于标记是否可以加锁,0代表没有人在用锁,1代表有人在占用,大于1说明这个锁被这个人加了多次【即重入锁概念】
            int c = getState();
            if (c == 0) {
                //CAS保证只有一个人能成功
                if (compareAndSetState(0, 1)) {
                    //设置持有锁的线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (getExclusiveOwnerThread() == current) { //走到这里说明有人持有了锁,但是可以判断持有的人是不是自己【可重入】
                if (++c < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //因为每一次重入都会导致State的值+1,所以解锁的时候对应要减1
                setState(c);
                return true;
            }
            return false;
        }
    
        //为子类留下的加锁逻辑的抽象方法
        abstract boolean initialTryLock();
    
        //核心加锁逻辑里面便是使用抽象方法进行加锁
        @ReservedStackAccess
        final void lock() {
            if (!initialTryLock())
                acquire(1);
        }
    
        @ReservedStackAccess
        final void lockInterruptibly() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            if (!initialTryLock())
                acquireInterruptibly(1);
        }
    
        @ReservedStackAccess
        final boolean tryLockNanos(long nanos) throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            return initialTryLock() || tryAcquireNanos(1, nanos);
        }
    
        //尝试释放锁
        @ReservedStackAccess
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (getExclusiveOwnerThread() != Thread.currentThread())
                throw new IllegalMonitorStateException();
            boolean free = (c == 0);
            if (free)
                setExclusiveOwnerThread(null);
            setState(c);
            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();
        }
    
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
    
        // Methods relayed from outer class
    
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
    
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }
    
        final boolean isLocked() {
            return getState() != 0;
        }
    
        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }
    复制代码

     

    【3】实现抽象的 Sync类 的公平锁 FairSync类部分

    复制代码
    static final class FairSync extends Sync {
        //尝试锁定方法
        final boolean initialTryLock() {
            Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //看得出来首先队列要为空,其次才是CAS加锁成功,才算能够持有锁 
                //也就是说队列不为空,连CAS加锁的资格都没有,所以十分公平
                if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (getExclusiveOwnerThread() == current) {
                if (++c < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(c);
                return true;
            }
            return false;
        }
    
        //尝试获取方法
        protected final boolean tryAcquire(int acquires) {
            if (getState() == 0 && !hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
    }
    
    //AbstractQueuedSynchronizer类#hasQueuedThreads方法
    //判断队列是否为空【由于AQS里面采用的是链表实现队列效果,所以是判断节点情况】
    public final boolean hasQueuedThreads() {
        for (Node p = tail, h = head; p != h && p != null; p = p.prev)
            if (p.status >= 0)
                return true;
        return false;
    }
    复制代码

     

    【4】实现抽象的 Sync类 的非公平锁 NonfairSync类部分

    复制代码
    //与公平的同步器进行比较的话,会发现,他们本质没什么区别,因为大多数走的都是抽象方法的逻辑和AQS的方法
    //最大的区别在于加锁的方式不同,公平方式,队列没人才去加锁;非公平方式,不管队列有没有人,都是直接去加锁,加到了就持有
    static final class NonfairSync extends Sync {
        //尝试锁定方法
        final boolean initialTryLock() {
            Thread current = Thread.currentThread();
            //直接尝试CAS获取加锁权利
            if (compareAndSetState(0, 1)) { // first attempt is unguarded
                setExclusiveOwnerThread(current);
                return true;
            } else if (getExclusiveOwnerThread() == current) {
                int c = getState() + 1;
                if (c < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(c);
                return true;
            } else
                return false;
        }
    
        //尝试获取方法
        protected final boolean tryAcquire(int acquires) {
            //判断是否有人持有锁,没有则去加锁
            if (getState() == 0 && compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
    }
    复制代码

     

  • 相关阅读:
    网络中的TCP协议与UDP协议
    Bootstrap5简单实现登录页:上下左右居中
    一文搞定 JVM 面试,教你吊打面试官~
    基于Java的健身俱乐部管理系统设计与实现(亮点:健身课程课程、会员下单、在线支付)
    获取dubbo源码编译并导入idea以及启动入门项目dubbo-demo
    ORA-55610: Invalid DDL statement on history-tracked table
    Java项目:springBoot+Mysql实现的校园二手在线交易平台系统
    main函数执行前执行和执行后执行
    python+django_vue旅游酒店预订出行订票系统pycharm项目lw
    hexo发生错误 Error: Spawn failed
  • 原文地址:https://www.cnblogs.com/chafry/p/16754842.html