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中MapStruct的使用详解

    Java中MapStruct的使用详解

    这篇文章主要介绍了Java中MapStruct的使用详解,mapstruct,全称是org.mapstruct.Mapper, 是将接口或抽象类标记为映射器,并通过MapStruct为这个接口或者抽象类生成一个实现类,大大简化了Java bean类型之间转换,需要的朋友可以参考下
    2023-09-09
  • 浅谈Java中的重载,重写,多态,静态绑定、动态绑定

    浅谈Java中的重载,重写,多态,静态绑定、动态绑定

    这篇文章主要介绍了浅谈Java中的重载,重写,多态,静态绑定、动态绑定,具有一定借鉴价值
    2018-01-01
  • Java设计模式之观察者模式

    Java设计模式之观察者模式

    这篇文章主要为大家介绍了Java观察者模式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2021-12-12
  • Java List接口与Iterator接口及foreach循环使用解析

    Java List接口与Iterator接口及foreach循环使用解析

    这篇文章主要介绍了Java List接口与Iterator接口及foreach循环,主要包括List接口与Iterator接口及foreach循环具体的使用方法和代码,需要的朋友可以参考下
    2022-04-04
  • Java中的HashMap内存泄漏问题详解

    Java中的HashMap内存泄漏问题详解

    这篇文章主要介绍了Java中的HashMap内存泄漏问题详解,WeakHashMap中的key是弱引用,如果再使用之后没有及时remove掉这个key,那么当GC时key就可能会被回收,导致key对应的value对象占用的内存无法回收进而导致内存泄漏,需要的朋友可以参考下
    2023-09-09
  • 如何用匿名内部类实现 Java 同步回调

    如何用匿名内部类实现 Java 同步回调

    这篇文章主要介绍了如何用匿名内部类实现 Java 同步回调,帮助大家更好的理解和学习Java,感兴趣的朋友可以了解下
    2020-10-10
  • 一篇文章带你从java字节码层理解i++和++i

    一篇文章带你从java字节码层理解i++和++i

    这篇文章带你从java字节码层理解i++和++i,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-09-09
  • 解析本地方法映射Java层的数据类型

    解析本地方法映射Java层的数据类型

    这篇文章给大家介绍了本地方法映射Java层的数据类型,包括基础类型映射,引用类型映射等等,对java层数据类型映射相关知识,感兴趣的朋友跟随脚本之家小编一起看看吧
    2018-03-03
  • spring mvc4的日期/数字格式化、枚举转换示例

    spring mvc4的日期/数字格式化、枚举转换示例

    本篇文章主要介绍了spring mvc4的日期/数字格式化、枚举转换示例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-01-01
  • Spring jackson原理及基本使用方法详解

    Spring jackson原理及基本使用方法详解

    这篇文章主要介绍了Spring jackson原理及基本使用方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10

最新评论