设为首页 加入收藏

TOP

高并发Java(9):锁的优化和注意事项(一)
2017-09-30 13:36:59 】 浏览:9780
Tags:并发 Java 优化 注意事项

1. 锁优化的思路和方法

高并发Java(1):前言中有提到并发的级别。

一旦用到锁,就说明这是阻塞式的,所以在并发度上一般来说都会比无锁的情况低一点。

这里提到的锁优化,是指在阻塞式的情况下,如何让性能不要变得太差。但是再怎么优化,一般来说性能都会比无锁的情况差一点。

这里要注意的是,在高并发Java(5):JDK并发包1中提到的ReentrantLock中的tryLock,偏向于一种无锁的方式,因为在tryLock判断时,并不会把自己挂起。

锁优化的思路和方法总结一下,有以下几种。

  • 减少锁持有时间
  • 减小锁粒度
  • 锁分离
  • 锁粗化
  • 锁消除

1.1 减少锁持有时间

public synchronized void syncMethod(){  
		othercode1();  
		mutextMethod();  
		othercode2(); 
	}

像上述代码这样,在进入方法前就要得到锁,其他线程就要在外面等待。

这里优化的一点在于,要减少其他线程等待的时间,所以,只用在有线程安全要求的程序上加锁

public void syncMethod(){  
		othercode1();  
		synchronized(this)
		{
			mutextMethod();  
		}
		othercode2(); 
	}

1.2 减小锁粒度

将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争。降低了锁的竞争,偏向锁,轻量级锁成功率才会提高。

最最典型的减小锁粒度的案例就是ConcurrentHashMap。这个在高并发Java(5):JDK并发包1有提到。

1.3 锁分离

最常见的锁分离就是读写锁ReadWriteLock,根据功能进行分离成读锁和写锁,这样读读不互斥,读写互斥,写写互斥,即保证了线程安全,又提高了性能,具体也请查看高并发Java(5):JDK并发包1

读写分离思想可以延伸,只要操作互不影响,锁就可以分离。

比如LinkedBlockingQueue

从头部取出,从尾部放数据。当然也类似于高并发Java(6):JDK并发包2中提到的ForkJoinPool中的工作窃取。

1.4 锁粗化

通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。只有这样,等待在这个锁上的其他线程才能尽早的获得资源执行任务。但是,凡事都有一个度,如果对同一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化 。

举个例子:

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

这种情况,根据锁粗化的思想,应该合并

public void demoMethod(){  
		//整合成一次锁请求 
		synchronized(lock){   
			//do sth.   
			//做其他不需要的同步的工作,但能很快执行完毕  
		}
	}

当然这是有前提的,前提就是中间的那些不需要同步的工作是很快执行完成的。

再举一个极端的例子:

for(int i=0;i<CIRCLE;i++){  
			synchronized(lock){  

			} 
		}

在一个循环内不同得获得锁。虽然JDK内部会对这个代码做些优化,但是还不如直接写成

synchronized(lock){ 
			for(int i=0;i<CIRCLE;i++){ 

			} 
		}

当然如果有需求说,这样的循环太久,需要给其他线程不要等待太久,那只能写成上面那种。如果没有这样类似的需求,还是直接写成下面那种比较好。

1.5 锁消除

锁消除是在编译器级别的事情。

在即时编译器时,如果发现不可能被共享的对象,则可以消除这些对象的锁操作。

也许你会觉得奇怪,既然有些对象不可能被多线程访问,那为什么要加锁呢?写代码时直接不加锁不就好了。

但是有时,这些锁并不是程序员所写的,有的是JDK实现中就有锁的,比如Vector和StringBuffer这样的类,它们中的很多方法都是有锁的。当我们在一些不会有线程安全的情况下使用这些类的方法时,达到某些条件时,编译器会将锁消除来提高性能。

比如:

public static void main(String args[]) throws InterruptedException {
		long start = System.currentTimeMillis();
		for (int i = 0; i < 2000000; i++) {
			createStringBuffer("JVM", "Diagnosis");
		}
		long bufferCost = System.currentTimeMillis() - start;
		System.out.println("craeteStringBuffer: " + bufferCost + " ms");
	}

	public static String createStringBuffer(String s1, String s2) {
		StringBuffer sb = new StringBuffer();
		sb.append(s1);
		sb.append(s2);
		return sb.toString();
	}

上述代码中的StringBuffer.append是一个同步操作,但是StringBuffer却是一个局部变量,并且方法也并没有把StringBuffer返回,所以不可能会有多线程去访问它。

那么此时StringBuffer中的同步操作就是没有意义的。

开启锁消除是在JVM参数上设置的,当然需要在server模式下:

-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks

并且要开启逃逸分析。 逃逸分析的作用呢,就是看看变量是否有可能逃出作用域的范围。

比如上述的StringBuffer,上述代码中craeteStringBuffer的返回是一个String,所以这个局部变量StringBuffer在其他地方都不会被使用。如果将craeteStringBuffer改成

public static StringBuffer craeteStringBuffer(String s1, String s2) {
		StringBuffer sb = new StringBuffer();
		sb.append(s1);
		sb.append(s2);
		return sb;
	}

那么这个 StringBuffer被返回后,是有可能被任何其他地方所使用的(譬如被主函数将返回结果put进map啊等等)。那么JVM的逃逸分析可以分析出,

首页 上一页 1 2 3 4 下一页 尾页 1/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇高并发Java(8):NIO和AIO 下一篇Tomcat5.5(6.0)配置-多域名绑定和..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目