EffectiveJava Item67 避免过度同步

// Reusable forwarding class - Page 84

package jdk.effectivejava.item67;
import java.util.*;

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(); }
}

  

// Broken - invokes alien method from synchronized block! - Page 265
package jdk.effectivejava.item67;
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;
    }
}

  

// Set obeserver callback interface - Page 266
package jdk.effectivejava.item67;
public interface SetObserver<E> {
    // Invoked when an element is added to the observable set
    void added(ObservableSet<E> set, E element);
}

  这是书中的例子,有一个ObervableSet 还有一个观察者接口.

Test2.java

// More complex test of ObservableSet - Page 267
package jdk.effectivejava.item67;
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);
    }
}

  Test2运行结果:

13
14
15
16
17
18
19
20
21
22
23
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at jdk.effectivejava.item67.ObservableSet.notifyElementAdded(ObservableSet.java:27)
at jdk.effectivejava.item67.ObservableSet.add(ObservableSet.java:62)
at jdk.effectivejava.item67.Test2.main(Test2.java:18)

原来是:当主线程执行到执行到添加23时,调用顺序是ObervableSet.add -> notifyElementAdded -> 然后执行匿名类的added方法.

虽然addObserver removeObserver notifyElementAdded 都会synchronized(observers) ,对共享资源进行保护,当然由于上面的添加,以及对于观察者的删除都是main线程操作的,虽然不会有别的线程参与,但是也是一种 "在遍历集合的过程中删除集合中的元素",因此会触发ConcurrentModificationException

现在来看Test3.java

package jdk.effectivejava.item67;

import java.io.IOException;
// 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(); 
//原来代码中并没有,这里是本人自己添加,对异常部分有修改,与源码并不完全一样,但是表达的意思是一致的.. // Thread t = new Thread(new Runnable() { // public void run() { // s.removeObserver(observer); // } // }); // t.start(); // t.join(); } catch (Exception ex) { throw new AssertionError(ex.getCause()); } finally { executor.shutdown(); } } } }); for (int i = 0; i < 100; i++) set.add(i); //System.out.println(); } }

 这个调用观察者方法的时候,是新建了一个线程池.然后新开了一个线程,并发的去删除

然后运行到添加23时候,就会悲剧的死锁了.这里说下死锁的原因

Main的调用堆栈是这样的

ObervableSet.add -> notifyElementAdded -> 然后执行匿名类的added方法 ->  新建一个线程池,然后新建一个线程("pool-thread-01")->然后等待pool-thread-01执完成

pool-thread-01 要做的就是删除添加的那个匿名类观察者

但是,notifyElementAdded是一个syn方法,会锁定observers数组,然后在等待pool-thread-01完成的时候,调用其sumbit(new Runnable(){..}).get()方法,get会阻塞调用它的线程,因此,它会等pool-thread-01完成删除任务,但是pool-thread-01并不会获取observers的锁,因为已经被主线程占有了,所以,死锁的就产生了.

 

怎么改呢?

现在我们为了方便查看,在ObservableSet类加一个方法,叫obsCount().返回观察者数目.

    public int obsCount(){
    	return observers.size();
    }

  将submit后面的get方法去掉 就可以了.

最后如下所示

package jdk.effectivejava.item67;

import java.io.IOException;
// 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);
                                System.out.println("Remove!");
                            }
                        });
                        //.get(); 
//                        Thread t = new Thread(new Runnable() {
//                            public void run() {
//                                s.removeObserver(observer);
//                            }
//                        });
//                        t.start();
//                        t.join();
                    } catch (Exception ex) {
                        throw new AssertionError(ex.getCause());
                    } finally {
                        executor.shutdown();
                    }
                }
            }
        });

        for (int i = 0; i < 100; i++){
        	set.add(i);
        	System.out.println(set.obsCount());
        }
            
        //System.out.println();
    }
}

  这样主线程新建一个线程之后把删除的任务交给新线程,然后自己并不等待执行结果,而是自己继续往下执行.然后CPU调度main和pool-thread-01,当pool-thread-01执行的时候,就会将观察者删除.

如果改成下面这样呢?会是什么结果?

package jdk.effectivejava.item67;

import java.io.IOException;
// 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);
//                                System.out.println("Remove!");
//                            }
//                        }).get(); 
                        Thread t = new Thread(()-> {
                            	 s.removeObserver(observer);
                            	 System.out.println("Remove!");
                        });
                        t.setPriority(1);//为了说明Removed!这一个出现位置并不确定,但是一定在23之后
                        t.start();
                        t.join();
                    } catch (Exception ex) {
                        throw new AssertionError(ex.getCause());
                    } finally {
                        executor.shutdown();
                    }
                }
            }
        });

        for (int i = 0; i < 100; i++){
        	set.add(i);
        	System.out.println(set.obsCount());
        }
            
        //System.out.println();
    }
}

  一样会死锁,因为main中调用t.join(),主线程还是会等子线程.这样解决方法就是把t.join()去掉.

 

posted @ 2016-11-04 16:44  Alex_92  阅读(294)  评论(0)    收藏  举报