Java SE 6同步性能优化

Java SE的每个版本都花费了大量的心思在同步性能优化上,Java SE 6也不例外。当多个线程需要同时访问共享的可变数据时,需要使用锁来同步多个线程的访问。根据竞争程度的不同,锁又可分为竞争性锁(contended lock)和非竞争性锁(uncontended lock)。由于大多数的锁都是非竞争性的,Java SE主要将精力用于优化非竞争性锁的性能,同时Java SE 6也优化了竞争性锁的性能。对竞争性锁优化的主要方法是自适应自旋(adaptive spinning),对非竞争性锁优化的方法有偏向性锁(Biased Locking)、锁粗化(Lock Coarsening)和锁取消(Lock Elision)。下面就一一谈下这四种优化同步性能的方法。

 

自适应自旋(adaptive spinning)

 

当两个线程竞争同一个锁时,其中一个线程获得锁,而另一个线程被阻塞直到锁被释放。通常的做法是操作系统悬挂起后一个线程,当前一个线程释放锁时唤醒后一个线程。这存在线程上下文切换的开销,另一种方法是后一个线程忙等待一段时间(通常就是几个时钟周期),这也就是自旋(spinning),当线程等待锁的时间极短时,这种方法比悬挂方法更有效率。Java SE 6使用自适应(adaptive)的技术进一步提高性能,它的主要思想是动态监控自旋成功/失败的比率,如果该几率比较大就使用自旋方法,否则就使用悬挂方法。自适应自旋技术主要是对多核系有效,它能避免使用悬挂方法所造成的线程上下文切换开销。

 

 

偏向性锁(Biased Locking)

 

偏向性锁主要基于这样的一个观察,一个锁在它的生命周期中通常只被一个线程所拥有。如果线程1拥有锁,这个锁就偏向(biase toward)于线程1,下次线程1需要再次获得锁时它就具有优先权。

 

注:偏向性锁对性能优化的原理我还并不是十分清楚,官方文章说偏向性锁能够避免一些原子操作,而这些原子操作十分耗时,但是我不明白为什么需要这些原子操作。这里的原子操作主要指的是CAS(Compare and Swap)。

 

 

锁粗化(Lock Coarsening)

 

当线程需要获得-释放锁,然后再获得-释放同一个锁,且第一次释放锁和第二交获得锁之间没有其它操作时,可以将两次获得-释放合并成一个获得-释放锁,这可以减少获得-释放锁的开销。例如,对下面的代码就可以使用锁粗化技术。

 

	void addThing(StringBuffer buf) {
		buf.append("thing 1");
		buf.append("thing 2");
	}

 

锁粗化带来的一个不好的效果就是线程占用锁的时间变长,这导致其他线程等待更长的时间。基于这种原因,锁粗化不能用于循环上。例如对于下面的代码就不能使用锁技术。

	void addRepeatedThing(StringBuffer buf, int count) {
		for (int i = 0; i < count; i++) {
			buf.append("thing");
		}
	}
 

 

锁取消(Lock Elision)

 

有时我们可以通过逸出分析(escape analysis)确定这个锁只能在一个线程使用,这时锁不起任何作用,可以完全取消这个锁。比如下面的锁就可以完全去掉,因为new Object()只能在一个线程中使用,别的线程不能访问到这个锁。

 

	synchronized (new Object()) {
		doSomeThing();
	}

 

上面是个伪造的例子,下面是个更加实际的例子。在这个例子中,StringBuffer对象sb是个局部变量,只能被当前线程所访问,因此对sb的所有操作都可以安全地取消掉同步,通过这种方法StringBuffer可以达到和StringBuilder几乎一样的性能。

 

	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append("field1: ").append(field1);
		sb.append(", field2: ").append(field2);
		return sb.toString();
	}

你可能感兴趣的:(java)