简述:
《Effective Java》第67条避免过度同步
知识点:
1. synchronized 集合的时候,删除集合元素出现,并发修改和死锁的问题
2. CopyOnWriteArray, 一种在写操作时都会进行拷贝的并发集合(concurrent collection)
代码:
ForwardingSet.java
package com.anialy.test.concurrency;
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 int size() {
return s.size();
}
public boolean isEmpty() {
return s.isEmpty();
}
public boolean contains(Object o) {
return s.contains(o);
}
public Iterator<E> iterator() {
return s.iterator();
}
public Object[] toArray() {
return s.toArray();
}
public <T> T[] toArray(T[] a) {
return s.toArray(a);
}
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 retainAll(Collection<?> c) {
return c.retainAll(c);
}
public boolean removeAll(Collection<?> c) {
return s.removeAll(c);
}
public void clear() {
s.clear();
}
}
ObservableSet.java
package com.anialy.test.concurrency;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
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 (observer) {
observers.add(observer);
}
}
public boolean removeObserver(SetObserver<E> observer){
synchronized (observer) {
return observers.remove(observer);
}
}
private void notifyElemetnAdded(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)
notifyElemetnAdded(element);
return added;
}
@Override
public boolean addAll(Collection<? extends E> c) {
boolean result = false;
for(E element : c){
result |= add(element);
}
return result;
}
}
SetObserver.java
package com.anialy.test.concurrency;
public interface SetObserver<E> {
void added(ObservableSet<E> set, E element);
}
Test.java
package com.anialy.test.concurrency;
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
ObservableSet<Integer> set =
new ObservableSet<Integer>(new HashSet<Integer>());
// add 方法调用后会触发notifyElemetnAdded(E element)方法
// 执行 SetObserver added方法
set.addObserver(new SetObserver<Integer>() {
public void added(ObservableSet<Integer> set, Integer element) {
System.out.println(element);
}
});
for(int i = 0; i < 100; i++){
set.add(i);
}
}
}
依次输出没有问题
然后,修改SetObserver的added方法,其中移除SetObserver接口,预期是希望此时的set集合不再继续打印剩余数字
package com.anialy.test.concurrency;
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
ObservableSet<Integer> set =
new ObservableSet<Integer>(new HashSet<Integer>());
// add 方法调用后会触发notifyElemetnAdded(E element)方法
// 执行 SetObserver added方法
set.addObserver(new SetObserver<Integer>() {
public void added(ObservableSet<Integer> set, Integer element) {
System.out.println(element);
if(element == 23)
set.removeObserver(this);
}
});
for(int i = 0; i < 100; i++){
set.add(i);
}
}
}
但实际的输出却是,
书中解释的原因:
我们正在企图遍历列表的过程中,讲一个元素从列表中删除,这是非法的。notifyElementAdded方法中的迭代是在一个同步块中,
可以防止并发的修改,但是无法防止迭代线程本身回调到可观察的集合中,也无法防止修改它的observers列表
之后使用Executors.newSingleThreadExecutor()库函数
package com.anialy.test.concurrency;
import java.util.HashSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
ObservableSet<Integer> set =
new ObservableSet<Integer>(new HashSet<Integer>());
// add 方法调用后会触发notifyElemetnAdded(E element)方法
// 执行 SetObserver added方法
set.addObserver(new SetObserver<Integer>() {
public void added(final ObservableSet<Integer> set, Integer element) {
System.out.println(element);
if(element == 23){
ExecutorService executor = Executors.newSingleThreadExecutor();
final SetObserver<Integer> observer = this;
try {
executor.submit(new Runnable(){
public void run() {
set.removeObserver(observer);
}
}).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
});
for(int i = 0; i < 100; i++){
set.add(i);
}
}
}
这一次没有异常,而是遭遇了死锁。后台线程调用set.removeObserver,企图锁定observers,但它无法获得该锁,因为主线程已经有锁了。
正如这里所示,
private void notifyElemetnAdded(E element){
synchronized (observers) {
for(SetObserver<E> observer : observers){
observer.added(this, element);
}
}
}
在这期间,主线程一直在等待后台线程来完成对观察这的删除。
解决方式:
1. 对于notifyElementAdded中的collection在遍历前做一个快照
private void notifyElemetnAdded(E element){
List<SetObserver<E>> snapshot = null;
synchronized (observers) {
snapshot = new ArrayList<SetObserver<E>>(observers);
}
for(SetObserver<E> observer : snapshot){
observer.added(this, element);
}
}
2. CopyOnWriteArrayList
其是ArrayList的一种变体, 通过重新拷贝整个底层数组,在这里实现所有的写操作
private final List<SetObserver<E>> observers =
new CopyOnWriteArrayList<SetObserver<E>>();
private void notifyElemetnAdded(E element){
for(SetObserver<E> observer : observers){
observer.added(this, element);
}
}
可以看看CopyOnWriteArrayList的remove及add操作,每个写操作都会进行拷贝,就不会出现之前锁住集合而后遍历的情况了
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices). Returns the element that was removed from the list.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}