Java高并发程序设计笔记(七)锁的优化

锁的竞争必然会导致程序的整体性能下降,为了将这种副作用降到最低,提出一些关于锁的使用建议:

1.减少锁的持有时间

以下段代码为例

public synchronized void syncMethod()
{
    otherCode1();
    mutextMethod();
    otherCode2();
}

在syncMethod方法中只有mutextMethod( )是需要同步的,而otherCode1( )和otherCode2( )不需要同步,如果otherMethod1( )和otherMethod2( )都是重量级的方法,则会花费较长的CPU时间。

一个较为优化的解决方案是,只在必要时进行同步, 这样就能明显减少线程持有锁的时间,提高系统吞吐量。

public void syncMethod(){
    otherMethod1();
    synchronized(this){
        mutextMethod();
    }
    otherMethod2();
}

在改进的代码中只对mutextMethod( )方法进行了同步,锁占用时间相对较短,因此能有较高的并行度。

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

2.减小锁粒度

减小锁粒度也是一种削弱多线程竞争的有效手段,典型场景就是ConcurrentHashMap类的实现。对于HashMap,一种自然的想法就是对整个hashmap加锁,必然可以得到一个线程安全的对象,但是这样做,被认为是锁的粒度太大。

对于ConcurrentHashMap,它内部进一步细分了若干小的HashMap,称之为段,默认情况下一个ConcurrentHashMap被细分为16个小段。

如果要在ConcurrentHashMap中增加一个新的key-value键值对,并不是将整个ConcurrentHashMap加锁,而是首先根据hashcode得到该表项应该被存放到哪个段中,然后对该段加锁,然后进行put( )操作。由于默认有16个段,在最好情况下可以同时接受16个线程同时插入(插入道不同段中),从而大大提高吞吐量。

但是这会引入一个新问题,即当系统需要引入全局锁时,其消耗的资源会比较多。因此获取全局信息的方法调用不频繁时,这种减小锁粒度的方法,才能真正意义上提高系统吞吐量。

所谓减小锁的粒度,就是指缩小锁定对象的范围,从而减少锁冲突的可能性,进而提高系统的并发能力。

3.使用读写分离锁代替独占锁

使用读写分离锁代替独占锁是减小锁力度的一种特殊情况,如果说concurrentHashMap是通过分割数据结构实现的,那么读写锁则是对系统功能点的分割。

在读多写少的场合,使用读写锁可以有效提升系统的并发能力。

4.锁分离

对独占锁进行分离,典型的例子就是LinkedBlockingQuene的实现,在LinkedBlockingQuene中分别用了两把锁,take( )和put( )分别实现了从队列中取得数据和向队列中增加数据,由于LinkedBlockingQuene是基于链表的,因此两个操作分别作用于队列的前端和尾端,在JDK实现中取而代之的是两把不同的锁,分离了take( )和put( )操作。

通过takeLock和putLock两把锁,LinkedBlockingQuene实现了读数据和写数据的分离,在真正意义上成为可并发的操作。

5.锁粗化

对于同一个锁不停地请求,同步,释放本身会消耗系统宝贵的资源,不利于性能的优化。为此,虚拟机在遇到一连串对同一个锁不断请求和释放操作的同时,会把所有的锁操作整合成对锁的一次请求,从而减少对锁的同步请求次数,这叫做锁的粗化。比如:

public void method(){
    synchronized(lock){
    //do sth
    }
    synchronized(lock){
    //do sth
    }
}

会被整合成如下形式

public void method(){
    synchronized(lock){
    //do sth
    }
}

你可能感兴趣的:(java,并发,优化,并发编程)