我们时常用同步来解决并发程序的安全性和活跃性等问题,虽然性能有些许降低,但保证了程序的运行。
而过度同步导致的问题不仅仅是性能问题,安全性、活跃性还有各种不确定的问题都有可能出现。
To avoid liveness and safety failures, never cede control to the client within a synchronized method or block.
我们需要保证在自己的代码中控制客户端(外部方法)以避免活跃性和安全性问题。
换句话说,不要在同步区域中调用无法确定行为的方法,因为线程之间的通信,这样会导致无法预料的结果。
那么如何给同步区域提供一个无法确定行为的外部方法?
一种比较简单的方式是调用某个抽象声明的行为,并在运行期赋予其实例。
比如观察者模式,每个观察者都由同一个抽象进行描述,被观察者通过同一种描述调用观察者的行为。
只是在这个例子中,我们需要在同步区域调用观察者的行为。
现在我打算在某个集合添加元素时提醒所有观察者,但由于add方法可能是self-use,这里先用一段composition,贴出代码仅为方便,阅读时请直接略过:
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) {
this.s = s;
}
public void clear() {
s.clear();
}
public boolean contains(Object o) {
return s.contains(o);
}
public boolean isEmpty() {
return s.isEmpty();
}
public int size() {
return s.size();
}
public Iterator<E> iterator() {
return s.iterator();
}
public boolean add(E e) {
return s.add(e);
}
public boolean remove(Object o) {
return s.remove(o);
}
public boolean containsAll(Collection<?> c) {
return s.containsAll(c);
}
public boolean addAll(Collection<? extends E> c) {
return s.addAll(c);
}
public boolean removeAll(Collection<?> c) {
return s.removeAll(c);
}
public boolean retainAll(Collection<?> c) {
return s.retainAll(c);
}
public Object[] toArray() {
return s.toArray();
}
public <T> T[] toArray(T[] a) {
return s.toArray(a);
}
@Override
public boolean equals(Object o) {
return s.equals(o);
}
@Override
public int hashCode() {
return s.hashCode();
}
@Override
public String toString() {
return s.toString();
}
}
下面是被观察的ObservableSet,问题的关键就是那个notifyElementAdded方法,其同步区域中的observe.added正是无法确定的行为:
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);
}
}
private void notifyElementAdded(E element) {
synchronized (observers) {
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);
return result;
}
}
不出意料的,观察者接口仅仅描述上面的added方法:
public interface SetObserver<E> {
void added(ObservableSet<E> set, E element);
}
不考虑任何复杂的情况,我们试着加入一个打印集合元素的observer,这段程序自然会打印0~99:
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);
}
});
for (int i = 0; i < 100; i++) set.add(i);
}
接着换个花样,试着在循环过程中移除该observer,不出意外地,会出现ConcurrentModificationException:
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);
}
既然observer不能自己移除自己,我们试着将移除该observer的工作交给其他线程试试:
public static void main(String[] args) {
ObservableSet<Integer> set = new ObservableSet<Integer>(
new HashSet<Integer>());
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);
}
注意这里使用的是Executor,这样做的主要是为了防止锁重入,如果在方法内部再成功获得锁则毫无意义,和之前例子没有差异。
但使用Executor将当前observer从集合中移除时却发生了死锁。
原因也很简单,executor的removeObserver希望获得锁,但是锁已被主线程占用,而主线程却等待executor执行结束。
而解决这一切的方法是不在同步区域内调用外来方法,即:
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);
}
(PS:对应Effective Java Second Edition Item 67)
转载于:https://blog.51cto.com/alvez/1545872