Java高并发下锁的优化详解

 更新时间:2024年01月10日 09:38:17   作者:爱coding的同学  
这篇文章主要介绍了Java高并发下锁的优化详解,锁是最常用的同步方法之一,在高并发的环境下,激烈的锁竞争会导致程序的性能下降,下面是一些关于锁的使用建议,可以把这种副作用降到最低,需要的朋友可以参考下

简述

锁是最常用的同步方法之一。在高并发的环境下,激烈的锁竞争会导致程序的性能下降。

下面是一些关于锁的使用建议,可以把这种副作用降到最低。

减少锁持有时间

对于使用锁进行并发控制的应用程序而言,在锁竞争过程中,单个线程对锁的持有时间与系统性能有着直接的关系。

如果线程持有锁的时间很长,那么相对地,锁的竞争程序也就越激烈。

应该尽可能地减少对某个锁的占有时间,以减少程序间互斥的可能。

public synchronized void syncMethod() {
		otherCode1();  	   //无同步控制需要
		needSynMethod();   //有同步控制需要
		otherCode2();      //无同步控制需要
	}
	
public void syncMethod2() {
		otherCode1();  	   //无同步控制需要
		synchronized(this) {
			needSynMethod();   //有同步控制需要
		}
		otherCode2();      //无同步控制需要
	}

说明:减少锁的持有时间有助于降低锁冲突的可能性,进而提升系统的并发能力。

减少锁粒度

减少锁粒度也是一种削弱多线程锁竞争的有效手段。这种技术典型的使用场景就是ConcurrentHashMap类的实现。ConcurrentHashMap和Hashtable主要区别就是围绕着锁的粒度以及如何锁,可以简单理解成把一个大的HashTable分解成多个,形成了锁分离。而Hashtable的实现方式是—锁整个hash表。concurrentHashMap内部细分了若干个小的hashMap,称之为段(segment),默认情况下,被细分为16个段。新增的时候根据key的hashcode计算出应该存放到哪一个段中,然后对这个段枷锁,完成put()操作。就是说,最多可以同时接收16个线程同时插入(前提是16个不同段插入),从而大大提高吞吐量。但是,减少锁粒度会引入一个新的问题,即:当系统需要取得全局锁时,其消耗的资源会比较多。需要遍历每一个段,对每一个段进行加锁,最后还要对每一个段进行解锁。concurrentHashMap的size()方法会先使用无锁的方式求和,如果失败才会尝试这种加锁的方法。所以,在高并发场合,size()的性能差于同步的Hashmap,适用于size()调用少的场合。 说明:所谓减少锁粒度,就是指缩小锁定对象的范围,从而减少锁冲突的可能性,进行提供系统的并发能力。

读写分离锁来替换独占锁

ReadWriteLock读写分离锁替代独占锁是减少锁粒度的一种特殊情况。减少锁粒度是通过分割数据结构来实现的,而读写锁则是对系统功能点的分割。 读操作本身不会影响数据的完整性和一致性。因此,理论上讲,在大部分情况下,应该可以允许多想成同时读。 读写锁的访问约束情况:读-读(非阻塞)、读-写(阻塞)、写-读(阻塞)、写-写(阻塞)

public class ReadWriteLockTest2 {
         public static void main(String[] args) {
             //创建一个锁对象
             ReadWriteLock lock = new ReentrantReadWriteLock(false);
             //创建一个线程池
             ExecutorService pool = Executors.newFixedThreadPool(2);
             //创建一些并发访问
             RWRun rw1 = new RWRun(lock,true);
             RWRun rw2 = new RWRun(lock,true);
             RWRun rw3 = new RWRun(lock,false);
             //在线程池中执行各个的操作
             pool.execute(rw1);
             pool.execute(rw2);
             pool.execute(rw3);
             //关闭线程池
             pool.shutdown();
         }
}
class RWRun implements Runnable {
         private ReadWriteLock myLock;                 //执行操作所需的锁对象
         private boolean ischeck;         //是否查询
         public RWRun(ReadWriteLock myLock, boolean ischeck) {
			super();
			this.myLock = myLock;
			this.ischeck = ischeck;
		}
		public void run() {
                 if (ischeck) {
                         //获取读锁
                         myLock.readLock().lock();
                         try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
                         //释放读锁
                         myLock.readLock().unlock();
                 } else {
                         //获取写锁
                         myLock.writeLock().lock();
                         try {
 							Thread.sleep(1000);
 						} catch (InterruptedException e) {
 							e.printStackTrace();
 						}
                         //释放写锁
                         myLock.writeLock().unlock();
                 }
         }
}

上面执行完的时间为2秒钟,如果是独占锁 就需要3秒钟 结论:在读多写少的场合,使用读写锁可以有效提升系统的并发能力。

锁分离

在LinkedBlockingQueue的实现中,take()和put()分别实现了从队列中取得数据和往队列中增加数据的功能。

虽然两个函数都对当前队列进行了修改操作,但由于是基于链表的,因此,两个操作分别作用于队列的前端和尾端,从理论上来说,两者并不冲突。

所以在JDK中,采用了两把不同的锁,分离了toke()和put()的操作,实现了可并发的操作。

//take()函数需要持有的锁
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
//put()函数需要持有的锁
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();

锁粗化

虚拟机在遇到一连串连续地对同一锁不断进行请求和释放的操作时,便会把所有的锁操作整合成对锁的一次请求,从而减少对锁的请求同步次数,这个操作叫做锁的粗化。

例子:在循环内请求锁时。

   for (int i =0 i < n; i++) {
           synchronized (lock) {
		doSomething();
	   }
   }

更加合理的做法应该是在外层只请求一次锁

到此这篇关于Java高并发下锁的优化详解的文章就介绍到这了,更多相关高并发下锁的优化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • JDK动态代理过程原理及手写实现详解

    JDK动态代理过程原理及手写实现详解

    这篇文章主要为大家介绍了JDK动态代理过程原理及手写实现详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • Java中左移和右移问题图文详解

    Java中左移和右移问题图文详解

    左移和右移并不常用,在一些特殊情况下才会使用,比如加解密时,会大量用到,这篇文章主要给大家介绍了关于Java中左移和右移问题的相关资料,需要的朋友可以参考下
    2021-11-11
  • Java使用Gateway自定义负载均衡过滤器

    Java使用Gateway自定义负载均衡过滤器

    这篇文章主要介绍了Java使用Gateway自定义负载均衡过滤器,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • java实现学生管理系统(面向对象)

    java实现学生管理系统(面向对象)

    这篇文章主要为大家详细介绍了java实现学生管理系统(面向对象),文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-03-03
  • Java:com.netflix.client.ClientException错误解决

    Java:com.netflix.client.ClientException错误解决

    本文主要介绍了Java:com.netflix.client.ClientException错误解决,主要是指出客户端 module-sso 试图通过负载均衡器访问服务时,负载均衡器没有找到可用的服务器来处理请求,下面就来介绍一下解决方法
    2024-08-08
  • JavaSE程序逻辑控制实现详细图文教程

    JavaSE程序逻辑控制实现详细图文教程

    JavaSE是为了开发桌面应用程序和控制台应用程序而设计的,使用JavaSE可以编写独立运行的Java应用程序,这篇文章主要给大家介绍了关于JavaSE程序逻辑控制实现的相关资料,需要的朋友可以参考下
    2024-04-04
  • Springmvc ResponseBody响应json数据实现过程

    Springmvc ResponseBody响应json数据实现过程

    这篇文章主要介绍了Springmvc ResponseBody响应json数据实现过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • IDEA将Java.class文件反编译为Java文件原理、实践与深度解析

    IDEA将Java.class文件反编译为Java文件原理、实践与深度解析

    本文详细介绍了如何使用IntelliJ IDEA进行.class到.java的反编译,包括基础概念、实用技巧、引擎配置、疑难问题解决以及生产环境实战案例,通过这些步骤,开发者可以有效地理解和学习Java代码,提升开发效率,感兴趣的朋友跟随小编一起看看吧
    2025-12-12
  • 基于SpringBoot+FastExcel的百万级数据导入导出完整方案

    基于SpringBoot+FastExcel的百万级数据导入导出完整方案

    本文详细介绍了在SpringBoot3.5.11+JDK17+MySQL环境下,使用FastExcel实现百万级数据的导入和导出,内容包括数据库表设计、百万测试数据生成、实体类与Mapper、配置文件、百万数据导入、百万数据导出、线程池、阻塞队列、CountDownLatch的设计思路与原理,以及接口测试方法
    2026-03-03
  • 使用Java程序模拟实现新冠病毒传染效果

    使用Java程序模拟实现新冠病毒传染效果

    这篇文章主要介绍了用Java程序模拟实现新冠病毒传染效果,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08

最新评论