天天看點

重複插入資料的另類解決思路

        在進行插入資料時,我們會先從資料庫查詢是否已經擁有該記錄,但是最後會發現這個判斷沒有任何效果,導緻這個判斷失效的原因有很多,比如事務沒有送出,或者多台伺服器都執行了相同的代碼,或者你的控制器(strut2的Action,springMVC的Controller等)是多執行個體的!

    該部落格旨在解決最後一種問題。

    知識補充:

    1、SpringMvc的Controller是預設是單例的,Struts2的Action是多例的,spring注入預設是單例的。

    2、synchronized(this){

                 同步代碼塊

           }

          a、與放在方法上修飾一樣,都是鎖住的對象,也就是說任何線程執行到這裡時都需要獲得該方法的對象鎖,然後才能執行括号裡面的代碼,稱為同步(任何時間都隻有一個線程在執行同步代碼塊)。

          b、如果該代碼執行時間特别長,其他線程都在等着代碼執行完,就是同步阻塞狀态。

          c、this也可以替換為其他的對象,與之類似,其他線程到此時,需要獲得該對象的鎖,才能執行同步代碼塊。

          d、特别的,如果方法上有static和synchronized關鍵字,這時就變成了類鎖,要執行同步代碼,各個線程之間就需要對類鎖競争,誰有類鎖才能夠執行。

    3、線程安全指的是多個線程之間的切換不會導緻程式執行完後出現多種結果。

        如果你使用Struts2、spring,你會發現這類似于多線程,一條請求進來建立一個Action,然後注入業務實作類。同一時間多個相同請求并發,就會有可能繞過你的重複判斷,即同一時間,每個線程都有可能查詢不到重複結果。

       解決方法:

       1、通常情況下,我們可以給判斷重複的字段上加上聯合主鍵來判斷唯一即可,但是很多時候我們不願意這樣做,因為會減少插入的效率。資料庫在每次插入時都會周遊資料來判斷是否唯一,不唯一就抛出異常,在大資料量大批量插入時,效率急劇下降。

       2、當然你可以直接給你的方法加上synchronized來鎖住插入方法的對象,任何人要執行插入的操作方法,就必須得到該方法的對象鎖,但是如果方法特别慢,就會同步阻塞。

    此時給出一種思路,用一個線程安全的辨別位來實作同步。

    流程:

    1、各個線程将自己需要修改的資源作為辨別位,這裡我們将可以用來判斷重複的字段值作為标志位。

    2、提供一個全局變量存放這些辨別位,對這個全局變量的操作必須保證線程安全,即任意時刻都隻能有一個線程修改、查找這個全局變量。

    3、每次進行插入操作時,先判斷是否全局變量含有自己的辨別位。沒有,放入自己的辨別位;有,說明在此之前已經有其他線程在執行這個方法。

    4、從資料庫裡查詢判斷重複。

    5、執行操作方法。

    6、執行完後,釋放掉辨別位,告訴其他相同辨別位的線程“你可以執行了!”,但是具體能不能夠執行,也得經過資料庫的重複判斷。

    請看代碼:

public class Cache {
	private static Map<String, String> map = new ConcurrentHashMap<String, String>();

	private Cache() {
	}

	public static Map<String,String> getInstance() {
		return map;
	}
}
           

上面是一個簡單的單例對象來作為全局變量,不同的是這個單例是一個Map類型的,我們可以存放多個辨別位,這樣不同辨別位的線程是可以執行的。

private boolean isInCache(String key){
        synchronized (Cache.getInstance())
        {
            if(Cache.getInstance().containsKey(key)){
                return true;
            }else{
            	Cache.getInstance().put(key, key);
            }
        }
       return false;
}
           

以上代碼是對全局變量操作的,讀者可以看到,synchronized可以保證線程安全,而且是對Map進行put和containsKey操作,效率極快,幾乎不會造成同步阻塞。參數key是由你自己定義的,合理使用就可以讓有插入相同資源想法的線程們變成同步的。(我這裡因為業務需求,orgId和classId兩個都相同說明是重複資料, 是以我将兩個值拼接起來作為标志位)

if (!isInCache(orgId + classId)){
	if('資料庫的重複判斷'){
			
	}
}
           

以上代碼就是對重複插入的判斷,外層判斷使線程不重複,内層判斷使資料不重複。

synchronized (Cache.getInstance()) {
	Cache.getInstance().remove(orgId + classId);
}
           

最後,代碼執行完後,請釋放資源,告訴其他相同想法的線程可以進來看一下,但是由于你已經執行了,資料庫已經有值了,内層判斷不會讓它執行的。當然你也可以不釋放,最後的結果是Map裡面的值越積越多~~

繼續閱讀