关于锁优化jvm已经做了非常多的努力,参见JVM层面的锁优化。我们平时编程自然不能完全依赖于jvm,在应用层面也有一些锁的优化措施:
减少锁持有的时间
缩短持有锁的时间,从而降低冲突。只在必要时才同步,举例:
public synchronized void syncMethod(){
othercode1();
mutexMethod();
othercode2();
}
这里只有mutexMethod()方法需要同步的话,而othercode1()和othercode2()又是重量级方法,这样会浪费大量的cpu时间,大并发情况下,这种对整个方法加锁的方案会导致等待线程大量增加。进行如下优化,只在必要时进行同步:
public void syncMethod2(){
othercode1();
synchronized(this){
mutexMethod();
}
othercode2();
}
这里只针对mutexMethod()同步,缩短锁占用时间。jdk中Pattern类也有类似的策略:
public Matcher matcher(CharSequence input) {
if (!compiled) {
synchronized(this) {
if (!compiled)
compile();
}
}
Matcher m = new Matcher(this, input);
return m;
}
这里只有表达式未编译时才进行加锁。
减小锁粒度
缩小锁定对象的范围,从而减少锁的冲突的可能性。
典型实现便是jdk1.7中的ConcurrentHashMap,将整个HashMap分为若干个段,若要增加一个元素,则根据hashcode得到所处的段,对该段加锁,而非将整个HashMap加锁。
但这种方式假如要获取全局量时会消耗较大资源,比如size()方法,获取该信息要取得所有子段的锁。
锁分离
典型实现是LinkedBlockingQueue,take()和put()方法实现队列的读数据和写数据,但是两个操作分别作用于队列的头和尾,并不冲突,所以实现中采用了两把锁——takeLock和putLock,实现了读数据和写数据的分离操作。源码如下:
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();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
/**
* Inserts the specified element at the tail of this queue, waiting if
* necessary for space to become available.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
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>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
/*
* Note that count is used in wait guard even though it is
* not protected by lock. This works because count can
* only decrease at this point (all other puts are shut
* out by lock), and we (or some other waiting put) are
* signalled if it ever changes from capacity. Similarly
* for all other uses of count in other wait guards.
*/
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
锁粗化
由第一条“减少锁持有时间”可知我们让每个线程持有锁的时间尽量短,但若是对同一个锁不停的请求、同步释放无疑又浪费资源了。这里要把握好度,具体如何取舍依实际情况而定。举例:
for(int i = 0;i<CIRCLE;i++){
synchronized(lock){}
}
这里每次循环都申请同一个锁,此时应该粗化成如下:
synchronized(lock){
for(int i = 0; i < CIRCLE;i++)}{}
}
无锁:CAS
原子操作
LongAdder
参考资料:
http://ifeve.com/atomiclong-and-longadder/
http://blog.youkuaiyun.com/yao123long/article/details/63683991