NingG +

Java并发:性能调优

21.9 性能调优

这个小节主要讲的是优化,但是高德纳不是说过嘛——Premature optimization is the root of all evil.所以等到真正发现有问题再仔细研究,现在看看估计就忘了。所以我先大概了解一下关于锁的问题,其他等遇到具体场景再说:)

1. 免锁容器

书中上来就先道出了免锁容器背后的通用策略:

对容器的修改可以与读操作同时发生,只要读取者只能看到完成修改的结果即可。修改是在容器数据结构的某个部分的一个单独的副本(比如 ConcurrentHashMap 分段锁表,那么就是一段的副本,有时是整个数据结构的副本)上执行的,并且这个副本在修改过程中是不可视的(不然读成脏数据了)。只有当修改完成时,被修改的结构才会自动地与主数据结构进行交换,之后读取者就可以看到这个修改了。 然后作者写了一个测试框架,旨在比较加锁容器和免锁容器在性能上的差异。很明显,免锁容器去掉了获取、销毁锁的开销,肯定会有性能的提升….

如果有兴趣,建议搜索一下 ConcurrentHashMap 相关的文章,知道它为什么能用于并发场景(因为有 N 个Segement 重入锁,每个 Segement 锁管理一个 HashEntry 数组),实现的效率如何(分段锁表而非锁整表),有什么弱点(弱一致性)?网上有很多分析源码的文章,大概了解一下原理也不是什么难事。

2. 乐观加锁

先说明为什么需要锁呢?在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突。这就是著名的并发性问题。典型的冲突有:

最常用的处理多用户并发访问的方法是加锁。当一个用户锁住数据库中的某个对象时,其他用户就不能再访问该对象。加锁对并发访问的影响体现在锁的粒度上。比如,放在一个表上的锁限制对整个表的并发访问;放在数据页上的锁限制了对整个数据页的访问;放在行上的锁只限制对该行的并发访问。可见行锁粒度最小,并发访问最好,页锁粒度最大,表锁介于2者之间。

下面通过一个小例子来说明 AtomicInteger 的乐观锁使用:

package concurrency;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

// 模拟遗传算法
public class FastSimulation {
 // 10万个基因
	static final int N_ELEMENTS = 100000;
	// 每个基因长度为30
	static final int N_GENES = 30;
	//一共进化50
	static final int N_EVOLVERS = 50;
	static final AtomicInteger[][] GRID = new AtomicInteger[N_ELEMENTS][N_GENES];

	static Random rand = new Random(47);

	static class Evolver implements Runnable {
		public void run() {
			while (!Thread.interrupted()) {
				int element = rand.nextInt(N_ELEMENTS);
				for (int i = 0; i < N_GENES; i++) {
					int previous = element - 1;
					if (previous < 0) {
						previous = N_ELEMENTS - 1;
					}
					int next = element + 1;
					if (next >= N_ELEMENTS) {
						next = 0;
					}

					int oldValue = GRID[element][i].get();
					// 前后三值取平均值
					int newValue = oldValue + GRID[previous][i].get() + GRID[next][i].get();
					newValue /= 3;

					// 乐观锁用法,因为没有任何锁机制。
					// 只有当想更新 GRID[element][i]的值,但是其值已经发生变化的情况下(不和 oldValue 相同), 才进行
					// 失败操作,这里只是打印一下。
					if (!GRID[element][i].compareAndSet(oldValue, newValue)) {
						System.out.println("Old value changed from " + oldValue + " to " + GRID[element][i]);
					}
				}
			}
		}
	}

	public static void main(String[] args) throws Exception {
		ExecutorService exec = Executors.newCachedThreadPool();
		for (int i = 0; i < N_ELEMENTS; i++)
			for (int j = 0; j < N_GENES; j++)
				GRID[i][j] = new AtomicInteger(rand.nextInt(1000));

		// 进化50
		for (int i = 0; i < N_EVOLVERS; i++)
			exec.execute(new Evolver());

		TimeUnit.SECONDS.sleep(5);
		exec.shutdownNow();
	}
}

3. ReadWriteLock

上面说明这个类的使用场景:

对向数据结构多读少写的情况进行了优化。使得可以有多个读取者,只要他们都不试图写入即可。如果写锁已经被其他任务持有,那么任何读取者都不能访问,直到这个写锁被释放。所以,这个性能到底如何是不能提前预知的,只有通过不断的测试你的程序性能才能验证。当然,前提是你知道这个类的应用场景:多读少写。 因为作者也说了,这是一个相当复杂的工具,只有当程序性能出现瓶颈的时候才会被考虑,所以这里有个大概的印象即可,另外还有一个 ReentrantReadWriteLock 工具类。

Top