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高并发下锁的优化详解的文章就介绍到这了,更多相关高并发下锁的优化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java并发编程专题(五)----详解(JUC)ReentrantLock

    java并发编程专题(五)----详解(JUC)ReentrantLock

    这篇文章主要介绍了java(JUC)ReentrantLock的的相关资料,文中讲解非常详细,实例代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • MyBatis几种不同类型传参的方式总结

    MyBatis几种不同类型传参的方式总结

    这篇文章主要介绍了MyBatis几种不同类型传参的方式总结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • Spring Boot实战之模板引擎

    Spring Boot实战之模板引擎

    这篇文章主要介绍了Spring Boot实战之模板引擎,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • Java获取指定父节点、子节点的方法实现

    Java获取指定父节点、子节点的方法实现

    在Java中,要获取指定节点的父节点和子节点,通常需要使用 DOM,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2024-02-02
  • Java对int[]数组做新增删除去重操作代码

    Java对int[]数组做新增删除去重操作代码

    这篇文章主要介绍了Java里面对int[]数组做新增删除去重实现,这里记录下使用int[]数组对数组进行新增删除去重等操作,用来更加了解java里面的集合类思想,需要的朋友可以参考下
    2023-10-10
  • 关于Java8新特性Optional类的详细解读

    关于Java8新特性Optional类的详细解读

    Optional类是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在,原来用 null 表示一个值不存在,现在Optional 可以更好的表达这个概念。并且可以避免空指针异常,需要的朋友可以参考下
    2023-05-05
  • Java使用opencv识别二维码的完整步骤

    Java使用opencv识别二维码的完整步骤

    OpenMV是一个开源,低成本,功能强大的机器视觉模块,下面这篇文章主要给大家介绍了关于Java使用opencv识别二维码的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2021-09-09
  • java实现excel导入数据的工具类

    java实现excel导入数据的工具类

    这篇文章主要介绍了java实现的excel导入数据的工具类,需要的朋友可以参考下
    2014-03-03
  • Java实现用户管理系统

    Java实现用户管理系统

    这篇文章主要为大家详细介绍了Java实现用户管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • java实现Dijkstra最短路径算法

    java实现Dijkstra最短路径算法

    这篇文章主要为大家详细介绍了java实现Dijkstra最短路径算法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01

最新评论