• 并发编程系列之Lock锁可重入性与公平性


    一、相似之处:Lock锁 vs Synchronized 代码块

    Lock锁是一种类似于synchronized 同步代码块的线程同步机制。从Java 5开始java.util.concurrent.locks引入了若干个Lock锁的实现类,所以通常情况下我们不需要实现自己的锁,重要的是需要知道如何使用它们,了解它们实现背后的原理。

    Lock锁API的基本使用方法和Synchronized 关键字大同小异,代码如下

    Lock lock = new ReentrantLock();  //实例化锁
    //lock.lock(); //上锁
    boolean locked = lock.tryLock();  //尝试上锁
    if(locked){
      try {
        //被锁定的同步代码块,同时只能被一个线程执行
      }finally {
        lock.unlock(); //放在finally代码块中,保证锁一定会被释放
      }
    }
    
    synchronized(obj){
        //被锁定的同步代码块,同时只能被一个线程执行
    }
    

    Lock锁使用看上去麻烦一点,但是java默认提供了很多Lock锁,能满足更多的应用场景。比如:基于信号量加锁、读写锁等等,关注我的专栏《java并发编程》,后续都会介绍。

    二、Lock接口中的方法

    Lock接口实现方法通常会维护一个计数器,当计数器=0的时候资源被释放,当计数器大于1的时候资源被锁定。

    public interface Lock {
        void lock();
        void lockInterruptibly() throws InterruptedException;
        boolean tryLock();
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
        void unlock();
        Condition newCondition();
    }
    
    • lock() - 调用该方法会使锁定计数器增加1,如果此时共享资源是空闲的,则将锁交给调用该方法的线程。
    • unlock() - 调用该方法使锁定计数器减少1,当锁定计数器为0时,资源被释放。
    • tryLock() - 如果该资源是空闲的,那么调用该方法将返回true,锁定计数器将增加1。如果资源处于被占用状态,那么该方法返回false,但是线程将不被阻塞。
    • tryLock(long timeout, TimeUnit unit) - 按照该方法尝试获得锁,如果资源此时被占用,线程在退出前等待一定的时间段,该时间段由该方法的参数定义,以期望在此时间内获得资源锁。
    • lockInterruptibly() - 如果资源是空闲的,该方法会获取锁,同时允许线程在获取资源时被其他线程打断。这意味着,如果当前线程正在等待一个锁,但其他线程要求获得该锁,那么当前线程将被中断,并立即返回不会获得锁。

    三、不同点:Lock锁 vs Synchronized 代码块

    使用synchronized同步块和使用Lock API 之间还是有一些区别的

    • 一个synchronized同步块必须完全包含在一个方法中 - 但Lock API的lock()和unlock()操作,可以在不同的方法中进行
    • synchronized同步块不支持公平性原则,任何线程都可以在释放后重新获得锁,不能指定优先级。但我们可以通过指定fairness 属性在Lock API中实现公平的优先级,可以实现等待时间最长的线程被赋予对锁的占有权。
    • 如果一个线程无法访问synchronized同步块,它就会被阻塞等待。Lock API提供了tryLock()方法,尝试获取锁对象,获取到锁返回true,否则返回false。返回false并不阻塞线程,所以使用该方法可以减少等待锁的线程的阻塞时间。

    四、锁的可重入性

    ”可重入“意味着某个线程可以安全地多次获得同一个锁对象,而不会造成死锁。

    4.1. synchronized锁的可重入性

    下面的代码synchronized代码块嵌套synchronized代码块,锁定同一个this对象,不会产生死锁。证明synchronized代码块针对同一个对象加锁,是可重入的

    public void testLock(){
        synchronized (this) {
          System.out.println("第1次获取锁,锁对象是:" + this);
          int index = 1;
          do {
            synchronized (this) {
              System.out.println("第" + (++index) + "次获取锁,锁对象是:" + this);
            }
          } while (index != 10);
        }
    }
    

    上面的这段代码输出结果是

    第1次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
    第2次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
    第3次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
    第4次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
    第5次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
    第6次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
    第7次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
    第8次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
    第9次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
    第10次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116
    

    4.2.ReentrantLock可重入锁

    Lock接口的实现类ReentrantLock,也是可重入锁。一般来说类名包含Reentrant的Lock接口实现类实现的锁都是可重入的。

    public void testLock1(){
      Lock lock = new ReentrantLock();  //实例化锁
      lock.lock();  //上锁
      System.out.println("第1次获取锁,锁对象是:" + lock);
      try {
        int index = 1;
        do {
          lock.lock();  //上锁
          try {
            System.out.println("第" + (++index) + "次获取锁,锁对象是:" + lock);
          }finally {
            lock.unlock();
          }
        } while (index != 10);
    
      }finally {
        lock.unlock(); //放在finally代码块中,保证锁一定会被释放
      }
    }
    

    当线程第一次获得锁的时候,计数器被设置为1。在解锁之前,该线程可以再次获得锁,每次计数器都会增加1。对于每一个解锁操作,计数器被递减1,当计数器为0时锁定资源被释放。所以最重要的是:lock(tryLock)要与unlock方法成对出现,即:在代码中加锁一次就必须解锁一次,否则就死锁

    五、Lock锁的公平性

    Java的synchronized 同步块对试图进入它们的线程,被授予访问权(占有权)的优先级顺序没有任何保证。因此如果许多线程不断争夺对同一个synchronized 同步块的访问权,就有可能有一个或多个线程从未被授予访问权。这就造成了所谓的 "线程饥饿"。为了避免这种情况,锁应该是公平的。

    Lock lock = new ReentrantLock(true);
    

    可重入锁提供了一个公平性参数fairness ,通过该参数Lock锁将遵守锁请求的顺序,即在一个线程解锁资源后,锁将被交给等待时间最长的线程。这种公平模式是通过在锁的构造函数中传递 "true "来设置的。

    欢迎关注我的博客,更多精品知识合集

    本文转载注明出处(必须带连接,不能只转文字):字母哥博客 - zimug.com

    觉得对您有帮助的话,帮我点赞、分享!您的支持是我不竭的创作动力!。另外,笔者最近一段时间输出了如下的精品内容,期待您的关注。

  • 相关阅读:
    python学习笔记之九:模块和包
    python学习笔记之八:迭代器和生成器
    python学习笔记之七:魔法方法,属性
    python学习笔记之六:更加抽象
    python学习笔记之五:抽象
    python学习笔记之四:条件,循环和其他语句
    python学习笔记之三:字典,当索引不好用时
    python学习笔记之二:使用字符串
    yolo自己的数据集中LabelImg的安装出现No module named 'libs.resources'错误
    django2 rest_framework + vue.js + mysql5.6 实现增删改查
  • 原文地址:https://www.cnblogs.com/zimug/p/16265300.html
Copyright © 2020-2023  润新知