zoukankan      html  css  js  c++  java
  • 并发编程之ReentrantLock

    正文

    ReentrantLock 是Java并发包中提供的一个可重入的互斥锁。ReentrantLock 和 synchronized 在基本用法和行为语义上基本相同,同样具有可重入性。只是 ReentrantLock 增加了一些高级扩展功能,比如,可以实现公平锁,同时也可以实现绑定多个 Condition。

    可重入性 / 公平锁 / 非公平锁

    1. 可重入性: 一个线程在获取一段使用锁的代码时,可以再次进入这段代码。例如,一个被可重入锁修饰的递归程序,可以重复的获取锁,而不会出现把自己锁死的情况。synchronized 和 ReentrantLock 都具有可重入性。

    2. 公平锁 / 非公平锁
      所谓公平锁,指锁的获取策略相对公平,当多个线程在获取同一个锁时,必须按照锁的申请时间来依次获得锁;非公平锁则不同。synchronized 是非公平锁,ReentrantLock 默认也是非公平的,但是可以通过带 boolean 参数的构造方法指定使用公平锁,但非公平锁的性能一般要优于公平锁。
      synchronized 是 Java 原生的互斥同步锁,使用方便,对于 synchronized 修饰的方法或同步块,无需再显式释放锁。而 ReentrantLock 做为 API 层面的互斥锁,需要显式地去加锁解锁。采用 Lock ,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用 Lock 必须在 try{}catch{} 块中进行,并且将释放锁的操作放在 finally 块中进行,以保证锁一定被被释放,防止死锁的发生。

    class Demo{
    
        private final ReentrantLock lock = new ReentrantLock();
    
        public void method(){
    
            lock.lock(); // 加锁
    
            try{
                // to do...
    
            }finally {
                lock.unlock(); // 释放锁
            }
        }
    }
    

    源码分析

    1. 无参构造函数,默认是非公平锁
    /**
         * Creates an instance of {@code ReentrantLock}.
         * This is equivalent to using {@code ReentrantLock(false)}.
         */
        public ReentrantLock() {
            sync = new NonfairSync();
        }
    
    1. 带 Boolean 参数的构造器
    /**
         * Creates an instance of {@code ReentrantLock} with the
         * given fairness policy.
         *
         * @param fair {@code true} if this lock should use a fair ordering policy
         */
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
    
    
    1. lock()
        public void lock() {
            sync.acquire(1); //代理到Sync的lock方法上
        }
    

    Sync的lock方法是抽象的,实际的lock会代理到FairSync或是NonFairSync上(根据用户的选择来决定,公平锁还是非公平锁)

    1. unlock()
        public void unlock() {
            sync.release(1);
        }
    
    

    释放锁,调用sync的release方法。

    1. tryLock()
    public void method(){
    
            if(lock.tryLock()){
    
                try{
    
                    // to do...
    
                }finally {
                    lock.unlock();
                }
            }else {
    
                // 如果不能获取锁,则做其他事情
            }
        }
    

    tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false。

    1. newCondition()
    public Condition newCondition() {
            return sync.newCondition();
    }
    

    获取一个conditon,ReentrantLock支持多个Condition

    示例

    /*
    * 编写一个程序,开启 3 个线程,这三个线程的 ID 分别为 A、B、C,每个线程将自己的 ID 在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。
    *    如:ABCABCABC…… 依次递归
    */
    class AlternateDemo{
    
        private int num = 1; // 标记当前正在执行的线程
    
        private ReentrantLock lock = new ReentrantLock();
    
        private Condition condition1 = lock.newCondition();
        private Condition condition2 = lock.newCondition();
        private Condition condition3 = lock.newCondition();
    
        /**
         *
         * @param totalLoops 循环第几轮
         */
        public void loopA(int totalLoops){
    
            lock.lock();
    
            try{
                if(num != 1){
                    condition1.await();
                }
    
                System.out.println(Thread.currentThread().getName() + "	" + totalLoops);
    
                num = 2;
                condition2.signal();
    
            }catch (Exception e){
    
            }finally {
                lock.unlock();
            }
        }
    
        public void loopB(int totalLoops){
    
            lock.lock();
    
            try{
                if(num != 2){
                    condition2.await();
                }
    
                System.out.println(Thread.currentThread().getName() + "	" + totalLoops);
    
                num = 3;
                condition3.signal();
    
            }catch (Exception e){
    
            }finally {
                lock.unlock();
            }
        }
    
        public void loopC(int totalLoops){
    
            lock.lock();
    
            try{
                if(num != 3){
                    condition3.await();
                }
    
                System.out.println(Thread.currentThread().getName() + "	" + totalLoops);
    
                num = 1;
                condition1.signal();
    
            }catch (Exception e){
    
            }finally {
                lock.unlock();
            }
        }
    }
    
    
    public class Main {
    
        public static void main(String[] args) {
    
            AlternateDemo alternateDemo = new AlternateDemo();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        alternateDemo.loopA(i);
                    }
                }
            }, "A").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        alternateDemo.loopB(i);
                    }
                }
            }, "B").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        alternateDemo.loopC(i);
                        System.out.println("----------------------");
                    }
                }
            }, "C").start();
        }
    }
    

    代码分析:三个线程A、B、C分别调用10次打印,只需想办法控制三个现成的执行顺序。
    若线程A先获取锁,直接打印。
    若线程B先获取锁,B会被阻塞,释放锁后由A、C争夺。
    若线程C先获取锁,C会被阻塞,释放锁后由A、B争夺。
    使用一个变量来维持获取锁的顺序,分别是线程A、线程B、线程C

    总结

    1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
    2. synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
    3. Lock类可以创建Condition对象,Condition对象用来是线程等待和唤醒线程,需要注意的是Condition对象的唤醒的是用同一个Condition执行await方法的线程,所以也就可以实现唤醒指定类的线程
    奋斗不一定成功,不奋斗一定不会成功!
  • 相关阅读:
    C# 应用程序性能监测器,很不错的工具
    C#接口(interface)与类(class)的实例互相转换深度解析
    异步与线程深度解析
    内存管理:程序是如何被优雅的装载到内存中
    MacBookM1通过DockerDesk安装MySQL8容器
    理解异步编程体系
    asp.net导入excel方法大集合
    无法激活服务,因为它不支持 ASP.NET 兼容性
    Oracle 数据库 在asp.net中 插入时间
    微软发布更新包 修复SilverLight中的6个问题
  • 原文地址:https://www.cnblogs.com/xucoding/p/11798846.html
Copyright © 2011-2022 走看看