這裡有一個輔助基礎類
package cn.xf.cp.ch02.item16; import java.util.Collection; import java.util.Iterator; import java.util.Set; public class ForwardingSet<E> implements Set<E> { /** * 這個類作為轉發類,內部通過復合的方式把set作為一個組件 */ private final Set<E> s; public ForwardingSet(Set<E> s) { this.s = s; } @Override public int size() { return s.size(); } @Override public boolean isEmpty() { return s.isEmpty(); } @Override public boolean contains(Object o) { return s.contains(o); } @Override public Iterator<E> iterator() { return s.iterator(); } @Override public Object[] toArray() { return s.toArray(); } @Override public <T> T[] toArray(T[] a) { return s.toArray(a); } @Override public boolean add(E e) { return s.add(e); } @Override public boolean remove(Object o) { return s.remove(o); } @Override public boolean containsAll(Collection<?> c) { return s.containsAll(c); } @Override public boolean addAll(Collection<? extends E> c) { return s.addAll(c); } @Override public boolean retainAll(Collection<?> c) { return s.retainAll(c); } @Override public boolean removeAll(Collection<?> c) { return s.removeAll(c); } @Override public void clear() { s.clear(); } }
然後使用一個類使這個類可以被觀察者注冊
package cn.xf.cp.ch02.item67; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import cn.xf.cp.ch02.item16.ForwardingSet; public class ObservableSet<E> extends ForwardingSet<E> { public ObservableSet(Set<E> s) { super(s); } 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); } } private void notifyElementAdded2(E element) { List<SetObserver<E>> snapshot = null; synchronized (observers) { //這裡拍一個快照,這樣我們遍歷的時候就不用對原來的集合進行上鎖了 snapshot = new ArrayList<SetObserver<E>>(observers); } for (SetObserver<E> observer : snapshot) observer.added(this, element); } @Override public boolean add(E e) { //調用父類函數添加到集合中 boolean added = super.add(e); if(added) { //添加成功,觀察者保存注冊對象 notifyElementAdded(e); } return added; } @Override public boolean addAll(Collection<? extends E> c) { boolean result = false; for(E element : c) { //做或運算,只要有一個add添加成功,那麼result就是true result |= add(element); } return result; } }
可以注意到,這個類中已經有一個地方有兩個方法,這個後面會將
觀察者:
package cn.xf.cp.ch02.item67; public interface SetObserver<E> { /** * 當一個元素添加到ObservableSet對象中的時候,調用 * @param set * @param element */ void added(ObservableSet<E> set, E element); }
測試:
package cn.xf.cp.ch02.item67; import java.util.HashSet; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { @org.junit.Test public void test() { //創建一個被觀察的對象 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); } @org.junit.Test public void test2() { 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) //到23,我們取消這個觀察者,但是會爆出異常,因為在迭代遍歷列表的時候我們自己修改了列表,這是非法的 s.removeObserver(this); } }); for (int i = 0; i < 100; i++) set.add(i); } /** * 那麼如何取消觀察者者呢???? * 我們使用另外的一個線程在23的時候刪除這個觀察者 */ @org.junit.Test public void test3() { ObservableSet<Integer> set = new ObservableSet<Integer>(new HashSet<Integer>()); set.addObserver(new SetObserver<Integer>() { @Override public void added(final ObservableSet<Integer> set, Integer element) { System.out.println(element); //如果是23 if(element == 23) { //線程池,創建單個線程的線程池,如果當前線程在執行任務時突然中斷,則會創建一個新的線程替代它繼續執行任務 ExecutorService executor = Executors.newSingleThreadExecutor(); final SetObserver<Integer> observer = this; try { executor.submit(new Runnable() { @Override public void run() { //這裡會死鎖 set.removeObserver(observer); } }).get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } finally { executor.shutdown(); } } } }); for (int i = 0; i < 100; i++) set.add(i); } }
結果:
測試2
這裡會拋出異常,為什麼,理由已經在類中寫了
測試3
這個會造成死鎖,要使用
讓我們外部調用的內部集合避免上鎖,而是建立一個快照,然後對快照進行上鎖
要點:就是外部調用函數,最好不要進入到同步區,簡而言之同步區盡量就在內部進行操作,同步區盡可能少的工作,獲得鎖,處理數據,釋放鎖,都在內部完成