Java并行程序基础(九)

  • 锁的优化

1.减小锁的持有时间

此处略

2.减小锁粒度

这种技术的典型的使用场景就是ConcurrentHashMap类的实现。它内部细分了若干个小的HashMap,称之为段(SEGMENT),默认情况下,一个ConcurrentHashMap被进一步细分为16个段。如果需要在ConcurrentHashMap中增加一个新的表项,并不是将整个HashMap加锁,而是首先根据hashcode得到该表项应该被存放到哪个段中,然后对该段加锁,并完成put()操作。在多线程环境中,如果多个线程同时进行put()操作,只要被加入的表项不存放在同一个段中,则线程便可以做到真正的并行。由于默认有16个段,因此,可以同时接受16个线程同时插入(如果都插入不同的段中),从而大大提高吞吐量。下面的代码显示put()操作的过程。

 public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);
        int j = (hash >>> segmentShift) & segmentMask;//根据key,获得对应的段的序号
        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
            s = ensureSegment(j); //得到段,然后将数据插入给定的段中
        return s.put(key, hash, value, false);
    }

减小锁粒度会引入一个新问题:当系统需要取得全局锁时,其消耗的资源会比较多。虽然ConcurrentHashMap的pull()方法很好的分离了锁,但是当试图访问ConcurrentHashMap全局信息时,就会需要同时取得所有段的锁方法能顺利实施。比如ConcurrentHashMap的size()方法,它将返回ConcurrentHashMap的全部有效表项之和。要获取这个信息需要取得所有子段的锁,size()代码如下:

sum=0;
for(int i=0;i<segments.length;i++)  //对所有的段加锁
  segments[i].lock();
for(int i=0;i<segments.length;i++)  //统计总数
  sum+=segments[i].count;
for(int i=0;i<segments.length;i++)  //释放所有的锁
  segments[i].unlock();

 

在高并发的场景下,ConcurrentHashMap的size()性能要差与同步的HashMap。所谓减少锁粒度,就是指缩小锁定范围,从而减少锁冲突的可能性,进而提高系统的并发能力。

3.读写锁分离

减少锁粒度是通过分割数据结构实现的,那么读写锁则是多系统功能点的分割。在读多写少的场景下,使用读写锁可以有效提升系统的并发能力。

例如LinkedBlockingQueue的实现中,take()和put()分别实现了从队列中取得数据和往队列中增加数据的功能,但是这两个操作分别作用于队列的前端和尾端,用锁分离并不冲突。

 public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        // Note: convention in all put/take/etc is to preset local var
        // holding count negative to indicate failure unless set.
        int c = -1;
        Node<E> node = new Node(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();    //不能有两个线程同时进行put()
        try {
          
            while (count.get() == capacity) {  //如果队列满了
                notFull.await();    //等待
            }
            enqueue(node);  //插入数据
            c = count.getAndIncrement();//更新总数,变量c是count加1前的值
            if (c + 1 < capacity)
                notFull.signal();  //有足够的空间,通知其他线程
        } finally {
            putLock.unlock();//释放锁
        }
        if (c == 0)
            signalNotEmpty();//插入成功后,通知take()操作取数据
    }
public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();//不能有两个线程同时取数据
        try {
            while (count.get() == 0) {//如果当前没有可用数据,一直等待
                notEmpty.await();//等待,put()操作的通知
            }
            x = dequeue(); //取得第一个数据
            c = count.getAndDecrement();//数量减1,原子操作,因为会和put()同时访问count.注意c是count减1前 
                //的值
            if (c > 1)
                notEmpty.signal();//通知其他take()操作
        } finally {
            takeLock.unlock();//锁释放
        }
        if (c == capacity)
            signalNotFull();//通知put()操作,已有空余空间
        return x;
    }

4.锁粗化

public void Method(){
   synchronized (lock) {
		// do sth.	
		}
    //做其他不需要的同步的工作,但能很快执行完毕
   synchronized (lock) {
		// do sth.	
		}
}

被整合成如下形式:

public void Method(){
   //整合成一次锁请求
   synchronized (lock) {
	//do sth.
    // 做其他不需要同步的工作,但能很快的执行完毕		
   }
}
for(int i=0;i<CIRCLE;i++){

  synchronized (lock) {
			
  }
}
//更加合理的方式是
synchronized {
  for(int i=0;i<CIRCLE;i++){
  
  }
}

 

 

转载于:https://my.oschina.net/projerry/blog/872692

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值