服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Java中ReentrantLock4种常见的坑

Java中ReentrantLock4种常见的坑

2022-12-19 20:35Java中文社群 Java教程

本文主要介绍了Java中ReentrantLock 4种常见的坑,ReentrantLock默认情况下为非公平锁,下文关于其更多详情需要的小伙伴可以参考一下

前言

JDK 1.5 之前 synchronized 的性能是比较低的,但在 JDK 1.5 中,官方推出一个重量级功能 Lock,一举改变了 Java 中锁的格局。JDK 1.5 之前当我们谈到锁时,只能使用内置锁 synchronized,但如今我们锁的实现又多了一种显式锁 Lock。

前面的文章我们已经介绍了 synchronized,详见以下列表:

《浅谈synchronized加锁this和class的区别》

《Java中的synchronized优化方法之锁膨胀机制》

Java中synchronized的4个优化技巧

所以本文咱们重点来看 Lock。

 

Lock 简介

Lock 是一个顶级接口,它的所有方法如下图所示:

Java中ReentrantLock4种常见的坑

它的子类列表如下:

Java中ReentrantLock4种常见的坑

我们通常会使用 ReentrantLock 来定义其实例,它们之间的关联如下图所示:

Java中ReentrantLock4种常见的坑

PS:Sync 是同步锁的意思,FairSync 是公平锁,NonfairSync 是非公平锁。

 

ReentrantLock 使用

学习任何一项技能都是先从使用开始的,所以我们也不例外,咱们先来看下 ReentrantLock 的基础使用:

public class LockExample {
  // 创建锁对象
  private final ReentrantLock lock = new ReentrantLock();
  public void method() {
      // 加锁操作
      lock.lock();
      try {
          // 业务代码......
      } finally {
          // 释放锁
          lock.unlock();
      }
  }
}

ReentrantLock 在创建之后,有两个关键性的操作:

  • 加锁操作:lock()
  • 释放锁操作:unlock()

 

ReentrantLock 中的坑

1.ReentrantLock 默认为非公平锁

很多人会认为(尤其是新手朋友),ReentrantLock 默认的实现是公平锁,其实并非如此,ReentrantLock 默认情况下为非公平锁(这主要是出于性能方面的考虑),

比如下面这段代码:

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
  // 创建锁对象
  private static final ReentrantLock lock = new ReentrantLock();
  public static void main(String[] args) {
      // 定义线程任务
      Runnable runnable = new Runnable() {
          @Override
          public void run() {
              // 加锁
              lock.lock();
              try {
                  // 打印执行线程的名字
                  System.out.println("线程:" + Thread.currentThread().getName());
              } finally {
                  // 释放锁
                  lock.unlock();
              }
          }
      };
      // 创建多个线程
      for (int i = 0; i < 10; i++) {
          new Thread(runnable).start();
      }
  }
}

以上程序的执行结果如下:

Java中ReentrantLock4种常见的坑

从上述执行的结果可以看出,ReentrantLock 默认情况下为非公平锁。因为线程的名称是根据创建的先后顺序递增的,所以如果是公平锁,那么线程的执行应该是有序递增的,但从上述的结果可以看出,线程的执行和打印是无序的,这说明 ReentrantLock 默认情况下为非公平锁。

想要将 ReentrantLock 设置为公平锁也很简单,只需要在创建 ReentrantLock 时,设置一个 true 的构造参数就可以了,如下代码所示:

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
  // 创建锁对象(公平锁)
  private static final ReentrantLock lock = new ReentrantLock(true);
  public static void main(String[] args) {
      // 定义线程任务
      Runnable runnable = new Runnable() {
          @Override
          public void run() {
              // 加锁
              lock.lock();
              try {
                  // 打印执行线程的名字
                  System.out.println("线程:" + Thread.currentThread().getName());
              } finally {
                  // 释放锁
                  lock.unlock();
              }
          }
      };
      // 创建多个线程
      for (int i = 0; i < 10; i++) {
          new Thread(runnable).start();
      }
  }
}

以上程序的执行结果如下:

Java中ReentrantLock4种常见的坑

从上述结果可以看出,当我们显式的给 ReentrantLock 设置了 true 的构造参数之后,ReentrantLock 就变成了公平锁,线程获取锁的顺序也变成有序的了。

其实从 ReentrantLock 的源码我们也可以看出它究竟是公平锁还是非公平锁,ReentrantLock 部分源码实现如下:

 public ReentrantLock() {
   sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
}

从上述源码中可以看出,默认情况下 ReentrantLock 会创建一个非公平锁,如果在创建时显式的设置构造参数的值为 true 时,它就会创建一个公平锁。

2.在 finally 中释放锁

使用 ReentrantLock 时一定要记得释放锁,否则就会导致该锁一直被占用,其他使用该锁的线程则会永久的等待下去,所以我们在使用 ReentrantLock 时,一定要在 finally 中释放锁,这样就可以保证锁一定会被释放。

反例

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
  // 创建锁对象
  private static final ReentrantLock lock = new ReentrantLock();
  public static void main(String[] args) {
      // 加锁操作
      lock.lock();
      System.out.println("Hello,ReentrantLock.");
      // 此处会报异常,导致锁不能正常释放
      int number = 1 / 0;
      // 释放锁
      lock.unlock();
      System.out.println("锁释放成功!");
  }
}

以上程序的执行结果如下:

Java中ReentrantLock4种常见的坑

从上述结果可以看出,当出现异常时锁未被正常释放,这样就会导致其他使用该锁的线程永久的处于等待状态。

正例

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
  // 创建锁对象
  private static final ReentrantLock lock = new ReentrantLock();
  public static void main(String[] args) {
      // 加锁操作
      lock.lock();
      try {
          System.out.println("Hello,ReentrantLock.");
          // 此处会报异常
          int number = 1 / 0;
      } finally {
          // 释放锁
          lock.unlock();
          System.out.println("锁释放成功!");
      }
  }
}

以上程序的执行结果如下:

Java中ReentrantLock4种常见的坑

从上述结果可以看出,虽然方法中出现了异常情况,但并不影响 ReentrantLock 锁的释放操作,这样其他使用此锁的线程就可以正常获取并运行了。

3.锁不能被释放多次

lock 操作的次数和 unlock 操作的次数必须一一对应,且不能出现一个锁被释放多次的情况,因为这样就会导致程序报错。

反例

一次 lock 对应了两次 unlock 操作,导致程序报错并终止执行,示例代码如下:

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
  // 创建锁对象
  private static final ReentrantLock lock = new ReentrantLock();
  public static void main(String[] args) {
      // 加锁操作
      lock.lock();

      // 第一次释放锁
      try {
          System.out.println("执行业务 1~");
          // 业务代码 1......
      } finally {
          // 释放锁
          lock.unlock();
          System.out.println("锁释锁");
      }

      // 第二次释放锁
      try {
          System.out.println("执行业务 2~");
          // 业务代码 2......
      } finally {
          // 释放锁
          lock.unlock();
          System.out.println("锁释锁");
      }
      // 最后的打印操作
      System.out.println("程序执行完成.");
  }
}

以上程序的执行结果如下:

Java中ReentrantLock4种常见的坑

从上述结果可以看出,执行第 2 个 unlock 时,程序报错并终止执行了,导致异常之后的代码都未正常执行。

4.lock 不要放在 try 代码内

在使用 ReentrantLock 时,需要注意不要将加锁操作放在 try 代码中,这样会导致未加锁成功就执行了释放锁的操作,从而导致程序执行异常。

反例

import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
  // 创建锁对象
  private static final ReentrantLock lock = new ReentrantLock();
  public static void main(String[] args) {
      try {
          // 此处异常
          int num = 1 / 0;
          // 加锁操作
          lock.lock();
      } finally {
          // 释放锁
          lock.unlock();
          System.out.println("锁释锁");
      }
      System.out.println("程序执行完成.");
  }
}

以上程序的执行结果如下:

Java中ReentrantLock4种常见的坑

从上述结果可以看出,如果将加锁操作放在 try 代码中,可能会导致两个问题:

  • 未加锁成功就执行了释放锁的操作,从而导致了新的异常;
  • 释放锁的异常会覆盖程序原有的异常,从而增加了排查问题的难度。

 

总结

本文介绍了 Java 中的显式锁 Lock 及其子类 ReentrantLock 的使用和注意事项,Lock 在 Java 中占据了锁的半壁江山,但在使用时却要注意 4 个问题:

  • 默认情况下 ReentrantLock 为非公平锁而非公平锁;
  • 加锁次数和释放锁次数一定要保持一致,否则会导致线程阻塞或程序异常;
  • 加锁操作一定要放在 try 代码之前,这样可以避免未加锁成功又释放锁的异常;
  • 释放锁一定要放在 finally 中,否则会导致线程阻塞。

到此这篇关于Java中ReentrantLock 4种常见的 坑的文章就介绍到这了,更多相关ReentrantLock 坑内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://juejin.cn/post/6995897974232612894

延伸 · 阅读

精彩推荐
  • Java教程Java简单从文件读取和输出的实例

    Java简单从文件读取和输出的实例

    下面小编就为大家带来一篇Java简单从文件读取和输出的实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    实例+练习+复习9062021-01-13
  • Java教程Java 数组分析及简单实例

    Java 数组分析及简单实例

    这篇文章主要介绍了Java 数组分析及简单实例的相关资料,在Java中它就是对象,一个比较特殊的对象,需要的朋友可以参考下...

    Java之家2322020-08-23
  • Java教程Java中字符串中连续相同字符去重方法

    Java中字符串中连续相同字符去重方法

    今天小编就为大家分享一篇Java中字符串中连续相同字符去重方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    asd1_1237492021-05-19
  • Java教程Java类的定义以及执行顺序学习教程

    Java类的定义以及执行顺序学习教程

    这篇文章主要介绍了Java类的定义以及执行顺序学习教程,包括对象的创建等面向对象编程的基础知识,需要的朋友可以参考下 ...

    goldensun3792020-01-06
  • Java教程吊打Java面试官之Lambda表达式 Stream API

    吊打Java面试官之Lambda表达式 Stream API

    这篇文章主要介绍了吊打Java之jdk8的新特性包括Lambda表达式、函数式接口、Stream API全面刨析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的...

    维斯布鲁克.猩猩10392021-12-20
  • Java教程3分钟带你彻底搞懂 Kafka

    3分钟带你彻底搞懂 Kafka

    实时数据处理,从名字上看,很好理解,就是将数据进行实时处理,在现在流行的微服务开发中,最常用实时数据处理平台有 RabbitMQ、RocketMQ 等消息中间件...

    Java极客技术6222021-06-18
  • Java教程java开发微信公众号支付

    java开发微信公众号支付

    这篇文章主要给大家结合微信支付接口开发的实践,从获取用户授权到各主要接口的使用方法等方面介绍微信支付的关键点技术,有需要的小伙伴可以参考下...

    hebedich3712020-01-02
  • Java教程Java 读写Properties配置文件详解

    Java 读写Properties配置文件详解

    这篇文章主要介绍了Java 读写Properties配置文件详解的相关资料,这里举例说明该如何实现,具有参考价值,需要的朋友可以参考下 ...

    java教程网3712020-07-06