我们时常用同步来解决并发程序的安全性和活跃性等问题,虽然性能有些许降低,但保证了程序的运行。

而过度同步导致的问题不仅仅是性能问题,安全性、活跃性还有各种不确定的问题都有可能出现。



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)