有助于提高锁性能的几点建议
-
减少锁持有的时间
只在有必要的地方加锁,这样就能减少锁的持有时间,增加系统的吞吐量
例如:在sycnMethon中只有mutextMethon方法需要加锁,如果other code1与other code2都是重量级方法的话,就会花费较长的CPU时间。如果在高并发的情况下,就会导致等待的线程不断增加。
public synchronized void sycnMethon(){ othercode1(); mutextMethon(); othercode2(); }
解决方案:只在mutextMethon方法上加锁,这样可以减少锁冲突的可能性,从而提升系统的吞吐量。
public void sycnMethon(){ othercode1(); synchronized(this){ mutextMethon(); } othercode2(); }
-
减小锁粒度
所谓减小锁粒度,就是缩小锁定对象的范围。
ConcurrentHashMap与HashTable如何将一个HashMap改造成一个线程安全的Map,我们知道HashTable是锁定整个对象,而ConcurrentHashMap是锁定一个段(也就是Hash Map的一部分)。所以说ConcurrentHashMap锁粒度比Hashtable小。
让我们来看一下ConcurrentHashMap怎样减小锁粒度的。
ConcurrentHashMap内部被分为多个小的HashMap,称之为段。在默认情况下,一个ConcurrentHashMap被分为16个段,如果需要在其中添加一个表项,不是将整个HashMap加上锁,而是根据该项的hashCode得到该表项被放在那个段,然后对该段进行加锁,并完成put操作。如果在多线程的情况下进行put操作,只要被加入的表项不是在同一个段,线程间便可以达到真正的并行。
-
用读写分离锁(ReadWriteLock)来代替独占锁
在读多少写的情况下,读写锁对系统很有好处。因为如果系统在读写数据的时候都用独占锁,那么读与读,读与写,写与写均不能做到并发,并且需要等待,而读操作不会影响到数据的完整性和一致性。因此,在大部分情况下,可以允许多线程同时读,读写锁体现了这个功能。
-
锁分离
如果将读写锁再进一步延申,那就是锁分离。读写锁根据读写操作功能上不同,进行了有效的锁分离。根据相似的分离思想,也可以对独占锁进行分离。一个典型的案例就是LinkedBlockingQueue的实现。其内部有两把锁,一把是去的数据的锁,一把是放入数据的锁。他们分别在take()和put()方法上使用。
/** Lock held by take, poll, etc */ private final ReentrantLock takeLock = new ReentrantLock(); /** Wait queue for waiting takes */ private final Condition notEmpty = takeLock.newCondition(); /** Lock held by put, offer, etc */ private final ReentrantLock putLock = new ReentrantLock(); /** Wait queue for waiting puts */ private final Condition notFull = putLock.newCondition();
虽然这两个方法同时对当前队列进行了修改操作,但由于该队列基于链表实现的,因此这两个操作分别作用于队列前端和尾端,两者并不冲突。
如果使用独占锁,take和put操作不能真正的并发,在运行时,他们会互相等待对方释放锁,这种情况下竞争比较激烈,会影响系统在高并发情况下的性能。
-
锁粗化
如果对一个锁不停的进行请求,同步和释放,其本身也会消耗系统的资源,反而不利于优化。为此虚拟机对这种情况下,便会对所有锁进行整合成对锁的一次请求,从而减少对锁的请求次数。
例如:
public void demoMethon(){ synchronized (lock){ do sth } 做其他不需要同步的工作,但能很快执行完毕 synchronized (lock){ do sth } }
上面代码会被整合成这样:
public void demoMethon(){ synchronized (lock){ do sth 做其他不需要同步的工作,但能很快执行完毕 do sth } }