高并发下锁的优化

简述

“锁”是最常用的同步方法之一。在高并发的环境下,激烈的锁竞争会导致程序的性能下降。下面是一些关于锁的使用建议,可以把这种副作用降到最低。

减少锁持有时间

对于使用锁进行并发控制的应用程序而言,在锁竞争过程中,单个线程对锁的持有时间与系统性能有着直接的关系。如果线程持有锁的时间很长,那么相对地,锁的竞争程序也就越激烈。应该尽可能地减少对某个锁的占有时间,以减少程序间互斥的可能。


       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();
	   }
   }

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱coding的同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值