第67条:避免过多同步
66条说了不同步的危险,本条讲的是过度同步的问题。过度同步,可能性能降低、死锁、或不确定的行为。
并发程序第一步要保证正确,第二步才是性能。
不能在同步区域内调用外部方法,缩小同步的代码范围。
反面教材
集合Iterator时不能 remove元素否则会产生并发修改异常
ConcurrentModificationException
// More complex test of ObservableSet - Page 267
import java.util.*;
public class Test2 {
public static void main(String[] args) {
ObservableSet<Integer> set =
new ObservableSet<Integer>(new HashSet<Integer>());
set.addObserver(new SetObserver<Integer>() {
public void added(ObservableSet<Integer> s, Integer e) {
System.out.println(e);
if (e == 23) s.removeObserver(this);
}
});
for (int i = 0; i < 100; i++)
set.add(i);
}
}
死锁例子
// Perverse test of ObservableSet - bottom of Page 267
import java.util.*;
import java.util.concurrent.*;
public class Test3 {
public static void main(String[] args) {
ObservableSet<Integer> set =
new ObservableSet<Integer>(new HashSet<Integer>());
// Observer that uses a background thread needlessly
set.addObserver(new SetObserver<Integer>() {
public void added(final ObservableSet<Integer> s, Integer e) {
System.out.println(e);
if (e == 23) {
ExecutorService executor =
Executors.newSingleThreadExecutor();
final SetObserver<Integer> observer = this;
try {
executor.submit(new Runnable() {
public void run() {
s.removeObserver(observer);
}
}).get();
} catch (ExecutionException ex) {
throw new AssertionError(ex.getCause());
} catch (InterruptedException ex) {
throw new AssertionError(ex.getCause());
} finally {
executor.shutdown();
}
}
}
});
for (int i = 0; i < 100; i++)
set.add(i);
}
}
// Broken - invokes alien method from synchronized block! - Page 265
import java.util.*;
import java.util.concurrent.*;
public class ObservableSet<E> extends ForwardingSet<E> {
public ObservableSet(Set<E> set) { super(set); }
private final List<SetObserver<E>> observers =
new ArrayList<SetObserver<E>>();
public void addObserver(SetObserver<E> observer) {
synchronized(observers) {
observers.add(observer);
}
}
public boolean removeObserver(SetObserver<E> observer) {
synchronized(observers) {
return observers.remove(observer);
}
}
// This method is the culprit
private void notifyElementAdded(E element) {
synchronized(observers) {
for (SetObserver<E> observer : observers)
observer.added(this, element);
}
}
// Alien method moved outside of synchronized block - open calls - Page 268
// private void notifyElementAdded(E element) {
// List<SetObserver<E>> snapshot = null;
// synchronized(observers) {
// snapshot = new ArrayList<SetObserver<E>>(observers);
// }
// for (SetObserver<E> observer : snapshot)
// observer.added(this, element);
// }
// Thread-safe observable set with CopyOnWriteArrayList - Page 269
//
// private final List<SetObserver<E>> observers =
// new CopyOnWriteArrayList<SetObserver<E>>();
//
// public void addObserver(SetObserver<E> observer) {
// observers.add(observer);
// }
// public boolean removeObserver(SetObserver<E> observer) {
// return observers.remove(observer);
// }
// private void notifyElementAdded(E element) {
// for (SetObserver<E> observer : observers)
// observer.added(this, element);
// }
@Override public boolean add(E element) {
boolean added = super.add(element);
if (added)
notifyElementAdded(element);
return added;
}
@Override public boolean addAll(Collection<? extends E> c) {
boolean result = false;
for (E element : c)
result |= add(element); // calls notifyElementAdded
return result;
}
}
集合添加对象 是同步方法,添加时又做其他删除的同步操作产生死锁。
删除对象的线程,想要获得对象锁,但是此对象已经被主线程锁了,所以产生死锁。
死锁最简单的例子
//代码一
class Deadlocker {
int field_1;
private Object lock_1 = new int[1];
int field_2;
private Object lock_2 = new int[1];
public void method1(int value) {
“synchronized” (lock_1) {
“synchronized” (lock_2) {
field_1 = 0; field_2 = 0;
}
}
}
public void method2(int value) {
“synchronized” (lock_2) {
“synchronized” (lock_1) {
field_1 = 0; field_2 = 0;
}
}
}
}
参考代码一,考虑下面的过程:
◆ 一个线程(ThreadA)调用method1()。
◆ ThreadA在lock_1上同步,但允许被抢先执行。
◆ 另一个线程(ThreadB)开始执行。
◆ ThreadB调用method2()。
◆ ThreadB获得lock_2,继续执行,企图获得lock_1。但ThreadB不能获得lock_1,因为ThreadA占有lock_1。
◆ 现在,ThreadB阻塞,因为它在等待ThreadA释放lock_1。
◆ 现在轮到ThreadA继续执行。ThreadA试图获得lock_2,但不能成功,因为lock_2已经被ThreadB占有了。
◆ ThreadA和ThreadB都被阻塞,程序死锁。