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()去掉.

浙公网安备 33010602011771号