避免过度同步(笔记)

简述:

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









  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值