1. 程式人生 > >避免過度同步(筆記)

避免過度同步(筆記)

簡述:

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



相關推薦

避免過度同步筆記

簡述: 《Effective Java》第67條避免過度同步 知識點: 1. synchronized 集合的時候,刪除集合元素出現,併發修改和死鎖的問題 2. CopyOnWriteArray, 一種在寫操作時都會進行拷貝的併發集合(concurrent collect

避免過度同步67

過度使用同步會導致效能低下、死鎖或其他不確定問題 在一個同步方法或程式碼塊中,不要放棄對客戶端的控制 即:在一個同步區域內部,不要呼叫被覆蓋方法,或者是傳入物件提供的方法  這些外來方法不可控,會帶來各種問題 為了避免死鎖和資料破壞,千萬不要從同步區域內部呼叫

多線程編程學習筆記——線程同步

als 執行 homepage auto using 發的 進行 read 函數 接上文 多線程編程學習筆記-基礎(一) 接上文 多線程編程學習筆記-基礎(二) 接上文 多線程編程學習筆記-基礎(三) 就如上一篇文章(多線程編程學習筆記-基礎(三))

多線程編程學習筆記——線程同步

class gen hybird 進行 syn locks finall nal nbsp 接上文 多線程編程學習筆記——線程同步(一) 接上文 多線程編程學習筆記——線程同步(二) 七、使用Barrier類

redis學習筆記——主從同步複製

在Redis中,使用者可以通過執行SLAVEOF命令或者設定slaveof選項,讓一個伺服器去複製(replicate)另一個伺服器,我們稱呼被複制的伺服器為主伺服器(master),而對主伺服器進行復制的伺服器則被稱為從伺服器(slave),如圖所示。 假設現在有兩個Redis伺服器,地

有道筆記無法同步筆錄

當經常翻牆或代理時,有些軟體或IE瀏覽器會自動修改一些配置項,導致有道筆記無法登入,具體修改了哪些配置已經不知道了,所以此時還原IE預設設定。因為我之前把IE功能關掉了,所以我現在把IE開啟 到此為止,重登入有道筆記,問題得到解決。

多執行緒程式設計學習筆記——執行緒同步

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; //引入執行緒 using System.Diagnostics; namesp

Nginx的安裝筆記

maximum 字符 erer min sse 最長路 gzip_vary plain bsp 0, 先決條件Nginx 依賴 pcre-devel, openssl, openssl-devel安裝命令:yum -y install pcre-devel openssl

筆記斯坦福機器學習第四講--牛頓法

ron 並不會 initial ant 結果 c函數 情況 對數 width 本講內容 1. Newton‘s method(牛頓法) 2. Exponential Family(指數簇) 3. Generalized Linear Models(GLMs)(廣義線性模型)

筆記斯坦福機器學習第六講--樸素貝葉斯

span || -h 沒有 height 單純 去除 變量 logistic 本講內容 1. Naive Bayes(樸素貝葉斯) 2.Event models(樸素貝葉斯的事件模型) 3.Neural network (神經網絡) 4.Support vector mac

筆記斯坦福機器學習第七講--最優間隔分類器

滿足 優化 最終 clas 定義 mar 擴展 strong play 本講內容 1.Optional margin classifier(最優間隔分類器) 2.primal/dual optimization(原始優化問題和對偶優化問題)KKT conditions(KK

linux高級技巧:rsync同步

十分 src 一次 art 文件夾 ont 關於 技巧 高級 1.配置兩個節點同步 上個帖子已經展示了同步一個主機的方法。這次我們再加入一個要同步的主機。方法上並無二質。 1.首先顯示我們的控制臺:

同步Synchronous和異步Asynchronous

就會 一個 方法調用 這一 開始 訂單 必須 通知 下單 同步和異步通常用來形容一次方法調用。同步方法調用一旦開始,調用者必須等到方法調用返回後,才能繼續後續的行為。異步方法調用更像一個消息的傳遞,一旦開始,方法調用就會立即返回,調用者就可以繼續後續的操作。而異步方法通常會

JAVA線程同步 信號量

ole 給定 如何 package 分配 exec 大量 復制代碼 離開 一個信號量有且僅有3種操作,且它們全部是原子的:初始化、增加和減少 增加可以為一個進程解除阻塞; 減少可以讓一個進程進入阻塞。 信號量維護一個許可集,若有必要,會在獲得許可之前阻塞每一個線程:

筆記java環境變量設置

java 環境變量配置windows系統: 我的電腦-->屬性--高級--環境變量,在這裏設置java的開發環境變量。 JAVA_HOME:配置JDK的目錄。 CLASSPATH:指定到哪裏去找運行時需要用到的類代碼(字節碼)。 PATH:指定可執行程序的位置。LINUX系統:

事件同步-——CreateEvent( )事件對象實現線程同步

reat strong 系統 col 線程等待 對象 span create 多個 ## 事件對象分為兩類:人工重置事件對象和自動重置事件對象。 對於人工重置事件對象,可以同時有多個線程等待到事件對象,成為可調度線程。 對於自動重置事件對象,等待該事件對象的多個線程只

火車票線程同步互斥體實現

eas 火車票 oid 主線程 ## code api 火車 ram ##占位 #include <windows.h> #include <iostream> int tickets = 100; HANDLE hMutex;

執行上下文筆記

用途 spa test src png 賦值 函數代碼 str ner 可執行代碼類型 全局代碼 函數代碼 eval代碼 執行上下文類型 全局上下文 函數上下文 eval上下文 執行上下文組成 作用域鏈 this 變量對象 進出棧順序 windo

默認路由與浮動路由,交換機配置筆記

switch interface 類型 前綴 網絡 配置ip fig port 鏈路狀態 一,默認路由默認路由是一種特殊的靜態路由,只有從路由表中找不到任何明確匹配的路由條目時,才會使用默認路由配置默認路由:當訪問Internet時,一些網絡出口只有一個,此時沒有必要配置。

web基礎知識梳理筆記

信息 空間 超文本 登錄訪問 trac xslt web基礎 request blog http(1.1)知識點 http協議概念 http協議是用於客戶端和服務器端之間的超文本傳輸協議,通過請求和響應實現通信,是一種無狀態協議(即對請求和響應不會做持久化處理)。 http