并发的优化
并发的优化
避免共享的可变状态
避免共享状态理想的情况是构造无状态的程序,没有状态自然也就不会共享。一个典型的例子就是
栈封闭
栈封闭是线程封闭的一种特例,在栈封闭中,只能通过局部变量访问对象,这些局部变量被封闭在执行线程的栈内部,其它线程无法访问到它们。
public int loadTheArk(Collection<Animal> candidates) {
SortedSet<Animal> animals;
int numPairs = 0;
Animal candidate = null;
// animals confined to method, don't let them escape!
animals = new TreeSet<Animal>(new SpeciesGenderComparator());
animals.addAll(candidates);
for (Animal a : animals) {
// ...
}
return numPairs;
}
在上面的代码中,
线程局部变量
private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
避免可变状态
线程安全性是不可变对象的固有属性,对于不可变对象,所有线程看到状态必然是一致的。纯函数式编程语言中,没有变量,只有常量,状态不能被持有,只能通过函数参数来传递,所以是天然的线程安全。
- 不要提供修改对象状态的方法
- 确保这个类不能被继承
- 把所有属性设置为
final - 把所有的属性设置为
private - 禁止访问类内部的可变域
锁优化
锁是在高并发的环境下为了保证数据的正确性而产生的同步手段,为了进一步提升性能,我们就需要对锁进行优化。锁的优化思路有:从减小锁的持有时间、锁的粒度的优化、锁分离、锁消除、无锁等等。
减小锁的持有时间
减小锁的持有时间是为了降低锁的冲突的可能性,提高体系的并发能力。
- 只在必要时进行同步加锁操作
例如下的代码:在加锁时先判断是否满足同步代码逻辑的要求,以达到减小锁的占有几率的目的。
// 使用条件判断减少锁持有时间提高效率。
public void matcher(Char input) {
if (!compiled) {
synchronized(this) {
if (!compiled) {
compile();
}
}
}
}
- 只在必须加锁的代码段加锁
下面的代码的执行只针对必须要加锁的代码段进行加锁操作,减少锁的占有的时间。
public synchronized void syncMethod() {
method1();
method2();
method3();
}
public void syncMethod() {
method1();
synchronized(this) {
method2();
}
method3();
}
锁粒度的优化
优化锁的粒度是根据实际的代码逻辑来进行判断,分为锁粒度的细化和锁粒度的粗化
- 锁粒度的细化
举个简单的例子,
但是减小锁的粒度也带来了新的问题,当锁粒度过于小的时候,获取全局锁消耗的资源也相应增加,以
- 锁粒度的粗化
在一般情况下,为了保证多线程之间的高效并发,会要求线程持有锁的时间尽量短,但是过度的细化会产生大量的申请和释放锁的操作,这对性能的影响也是非常大的。如下所示:
for(int i = 0; i < 10000; i++) {
synchronized(this) {
todo();
}
}
synchronized(this) {
for(int i = 0; i < 10000; i++) {
todo();
}
}
锁分离
根据实际的操作来选择加上不同的锁也是提升性能的重要方式之一。
读写分离锁替代独占锁
重入锁和内部锁
重入锁的使用相较于内部锁更加复杂,重入锁必须手动显示释放锁,内部锁则可以自动释放,重入锁提供了一套提高性能的功能和
自旋锁
自旋锁是
但是自旋锁只适用于线程竞争相对小、锁占用时间短的代码,对于锁竞争激烈的系统中不仅浪费了
无锁
锁是一种对操作的同步手段,但是也不是唯一的手段,例如使用空间换时间的思路同样可以解决问题,非阻塞的同步方式也可以达到并发的目的。最简单的一种非阻塞的同步就是
在
public final int getAndSet(int newValue) {
for (;;) { // 不停循环直到成功
int current = get(); // 获取当前的值
if (compareAndSet(current, newValue)) { // 若当前的值未受其他线程影响,则设置为新值
return current; // 返回新值
}
}
}
以时间换空间、以空间换时间都是实现代码的常用思路,在不同的地方应该使用不同的方式去达到业务需求。