Java并发编程如何降低锁粒度并实现性能优化

 更新时间:2020年08月29日 08:35:43   作者:程序零世界  
这篇文章主要介绍了Java并发编程如何降低锁粒度并实现性能优化,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

在高负载多线程应用中性能是非常重要的。为了达到更好的性能,开发者必须意识到并发的重要性。当我们需要使用并发时, 常常有一个资源必须被两个或多个线程共享。

在这种情况下,就存在一个竞争条件,也就是其中一个线程可以得到锁(锁与特定资源绑定),其他想要得到锁的线程会被阻塞。这个同步机制的实现是有代价的,为了向你提供一个好用的同步模型,JVM和操作系统都要消耗资源。有三个最重要的因素使并发的实现会消耗大量资源,它们是:

  • 上下文切换
  • 内存同步
  • 阻塞

为了写出针对同步的优化代码,你必须认识到这三个因素以及如何减少它们。在写这样的代码时你需要注意很多东西。在本文中,我会向你介绍一种通过降低锁粒度的技术来减少这些因素。
让我们从一个基本原则开始:不要长时间持有不必要的锁。

在获得锁之前做完所有需要做的事,只把锁用在需要同步的资源上,用完之后立即释放它。我们来看一个简单的例子:

public class HelloSync {
  private Map dictionary = new HashMap();
  public synchronized void borringDeveloper(String key, String value) {
    long startTime = (new java.util.Date()).getTime();
    value = value + "_"+startTime;
    dictionary.put(key, value);
    System.out.println("I did this in "+
   ((new java.util.Date()).getTime() - startTime)+" miliseconds");
  }
}

在这个例子中,我们违反了基本原则,因为我们创建了两个Date对象,调用了System.out.println(),还做了很多次String连接操作,但唯一需要做同步的操作是“dictionary.put(key, value);”。让我们来修改代码,把同步方法变成只包含这句的同步块,得到下面更优化的代码:

public class HelloSync {
  private Map dictionary = new HashMap();
  public void borringDeveloper(String key, String value) {
    long startTime = (new java.util.Date()).getTime();
    value = value + "_"+startTime;
    synchronized (dictionary) {
      dictionary.put(key, value);
    }
    System.out.println("I did this in "+
 ((new java.util.Date()).getTime() - startTime)+" miliseconds");
  }
}

上面的代码可以进一步优化,但这里只想传达出这种想法。如果你对如何进一步优化感兴趣,请参考java.util.concurrent.ConcurrentHashMap.

那么,我们怎么降低锁粒度呢?简单来说,就是通过尽可能少的请求锁。基本的想法是,分别用不同的锁来保护同一个类中多个独立的状态变量,而不是对整个类域只使用一个锁。我们来看下面这个我在很多应用中见到过的简单例子:

public class Grocery {
  private final ArrayList fruits = new ArrayList();
  private final ArrayList vegetables = new ArrayList();
  public synchronized void addFruit(int index, String fruit) {
    fruits.add(index, fruit);
  }
  public synchronized void removeFruit(int index) {
    fruits.remove(index);
  }
  public synchronized void addVegetable(int index, String vegetable) {
    vegetables.add(index, vegetable);
  }
  public synchronized void removeVegetable(int index) {
    vegetables.remove(index);
  }
}

杂货店主可以对他的杂货铺中的蔬菜和水果进行添加/删除操作。上面对杂货铺的实现,通过基本的Grocery 锁来保护fruits和vegetables,因为同步是在方法域完成的。事实上,我们可以不使用这个大范围的锁,而是针对每个资源(fruits和vegetables)分别使用一个锁。来看一下改进后的代码:

public class Grocery {
  private final ArrayList fruits = new ArrayList();
  private final ArrayList vegetables = new ArrayList();
  public void addFruit(int index, String fruit) {
    synchronized(fruits) fruits.add(index, fruit);
  }
  public void removeFruit(int index) {
    synchronized(fruits) {fruits.remove(index);}
  }
  public void addVegetable(int index, String vegetable) {
    synchronized(vegetables) vegetables.add(index, vegetable);
  }
  public void removeVegetable(int index) {
    synchronized(vegetables) vegetables.remove(index);
  }
}

在使用了两个锁后(把锁分离),我们会发现比起之前用一个整体锁,锁阻塞的情况更少了。当我们把这个技术用在有中度锁争抢的锁上时,优化提升会更明显。如果把该方法应用到轻微锁争抢的锁上,改进虽然比较小,但还是有效果的。但是如果把它用在有重度锁争抢的锁上时,你必须认识到结果并非总是更好。

请有选择性的使用这个技术。如果你怀疑一个锁是重度争抢锁请按下面的方法来确认是否使用上面的技术:

  • 确认你的产品会有多少争抢度,将这个争抢度乘以三倍或五倍(甚至10倍,如果你想准备的万无一失)
  • 基于这个争抢度做适当的测试
  • 比较两种方案的测试结果,然后挑选出最合适的.

用于改进同步性能的技术还有很多,但对所有的技术来说最基本的原则只有一个:不要长时间持有不必要的锁。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • 在Spring MVC中使用@ControllerAdvice创建全局异常处理器的方法

    在Spring MVC中使用@ControllerAdvice创建全局异常处理器的方法

    在Spring MVC中,可以使用@ControllerAdvice或@RestControllerAdvice注解来定义全局异常处理器类,并使用 @ExceptionHandler注解来定义处理特定异常的方法,本文就给大家介绍了Spring MVC @ControllerAdvice创建处理器的方法,需要的朋友可以参考下
    2023-08-08
  • SpringBoot基于AOP的本地/远程调用动态路由实践指南

    SpringBoot基于AOP的本地/远程调用动态路由实践指南

    这篇文章主要为大家详细介绍了SpringBoot基于AOP的本地/远程调用动态路由实践指南,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-07-07
  • 浅谈spring的重试机制无效@Retryable@EnableRetry

    浅谈spring的重试机制无效@Retryable@EnableRetry

    这篇文章主要介绍了浅谈spring的重试机制无效@Retryable@EnableRetry,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Java Mail与Apache Mail发送邮件示例

    Java Mail与Apache Mail发送邮件示例

    这篇文章主要介绍了Java Mail与Apache Mail发送邮件示例的相关资料,需要的朋友可以参考下
    2014-10-10
  • 了解java中的Clojure如何抽象并发性和共享状态

    了解java中的Clojure如何抽象并发性和共享状态

    Clojure是一种运行在Java平台上的 Lisp 方言,Lisp是一种以表达性和功能强大著称的编程语言,但人们通常认为它不太适合应用于一般情况,而Clojure的出现彻底改变了这一现状。,需要的朋友可以参考下
    2019-06-06
  • Java9新特性中的模块化详解

    Java9新特性中的模块化详解

    今天介绍一个Java 9的功能,模块化(Modular),这可能使Java有史以来最大的Feature,对Java9模块化相关知识感兴趣的朋友一起看看吧
    2022-03-03
  • SparkSQL中的JSON内置函数全解析

    SparkSQL中的JSON内置函数全解析

    你是否曾经为处理JSON数据而头疼?SparkSQL为我们提供了强大的内置JSON函数,让JSON处理变得轻而易举,本文将带你深入了解这些函数,感兴趣的朋友一起看看吧
    2024-08-08
  • springmvc之获取参数的方法(必看)

    springmvc之获取参数的方法(必看)

    下面小编就为大家带来一篇springmvc之获取参数的方法(必看)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • Java实现读取CSV文件并将数据放入对象

    Java实现读取CSV文件并将数据放入对象

    CSV文件是一种常见的数据存储格式,广泛应用于数据交换,日志记录和表格数据处理,在Java开发中,读取CSV文件并将数据映射到对象中是常见的需求,本文将详细介绍几种常见方法,有需要的可以了解下
    2025-06-06
  • 部署在linux上的java服务老是挂掉问题排查日志

    部署在linux上的java服务老是挂掉问题排查日志

    当服务器挂掉时,操作系统通常会记录一些异常信息,检查服务器的系统日志可以帮助定位问题所在,这篇文章主要介绍了部署在linux上的java服务老是挂掉问题排查日志的相关资料,需要的朋友可以参考下
    2025-10-10

最新评论