在進行插入數(shù)據(jù)時,我們會先從數(shù)據(jù)庫查詢是否已經(jīng)擁有該記錄,但是最后會發(fā)現(xiàn)這個判斷沒有任何效果,導致這個判斷失效的原因有很多,比如事務沒有提交,或者多臺服務器都執(zhí)行了相同的代碼,或者你的控制器(strut2的Action,springMVC的Controller等)是多實例的!
該博客旨在解決最后一種問題。
知識補充:
1、SpringMvc的Controller是默認是單例的,Struts2的Action是多例的,spring注入默認是單例的。
2、synchronized(this){
同步代碼塊
}
a、與放在方法上修飾一樣,都是鎖住的對象,也就是說任何線程執(zhí)行到這里時都需要獲得該方法的對象鎖,然后才能執(zhí)行括號里面的代碼,稱為同步(任何時間都只有一個線程在執(zhí)行同步代碼塊)。
b、如果該代碼執(zhí)行時間特別長,其他線程都在等著代碼執(zhí)行完,就是同步阻塞狀態(tài)。
c、this也可以替換為其他的對象,與之類似,其他線程到此時,需要獲得該對象的鎖,才能執(zhí)行同步代碼塊。
d、特別的,如果方法上有static和synchronized關鍵字,這時就變成了類鎖,要執(zhí)行同步代碼,各個線程之間就需要對類鎖競爭,誰有類鎖才能夠執(zhí)行。
3、線程安全指的是多個線程之間的切換不會導致程序執(zhí)行完后出現(xiàn)多種結果。
如果你使用Struts2、spring,你會發(fā)現(xiàn)這類似于多線程,一條請求進來新建一個Action,然后注入業(yè)務實現(xiàn)類。同一時間多個相同請求并發(fā),就會有可能繞過你的重復判斷,即同一時間,每個線程都有可能查詢不到重復結果。
解決方法:
1、通常情況下,我們可以給判斷重復的字段上加上聯(lián)合主鍵來判斷唯一即可,但是很多時候我們不愿意這樣做,因為會減少插入的效率。數(shù)據(jù)庫在每次插入時都會遍歷數(shù)據(jù)來判斷是否唯一,不唯一就拋出異常,在大數(shù)據(jù)量大批量插入時,效率急劇下降。
2、當然你可以直接給你的方法加上synchronized來鎖住插入方法的對象,任何人要執(zhí)行插入的操作方法,就必須得到該方法的對象鎖,但是如果方法特別慢,就會同步阻塞。
此時給出一種思路,用一個線程安全的標識位來實現(xiàn)同步。
流程:
1、各個線程將自己需要修改的資源作為標識位,這里我們將可以用來判斷重復的字段值作為標志位。
2、提供一個全局變量存放這些標識位,對這個全局變量的操作必須保證線程安全,即任意時刻都只能有一個線程修改、查找這個全局變量。
3、每次進行插入操作時,先判斷是否全局變量含有自己的標識位。沒有,放入自己的標識位;有,說明在此之前已經(jīng)有其他線程在執(zhí)行這個方法。
4、從數(shù)據(jù)庫里查詢判斷重復。
5、執(zhí)行操作方法。
6、執(zhí)行完后,釋放掉標識位,告訴其他相同標識位的線程“你可以執(zhí)行了!”,但是具體能不能夠執(zhí)行,也得經(jīng)過數(shù)據(jù)庫的重復判斷。
請看代碼:
- public class Cache {
- private static Map<String, String> map = new ConcurrentHashMap<String, String>();
-
- private Cache() {
- }
-
- public static Map<String,String> getInstance() {
- return map;
- }
- }
上面是一個簡單的單例對象來作為全局變量,不同的是這個單例是一個Map類型的,我們可以存放多個標識位,這樣不同標識位的線程是可以執(zhí)行的。
- 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操作,效率極快,幾乎不會造成同步阻塞。參數(shù)key是由你自己定義的,合理使用就可以讓有插入相同資源想法的線程們變成同步的。(我這里因為業(yè)務需求,orgId和classId兩個都相同說明是重復數(shù)據(jù), 所以我將兩個值拼接起來作為標志位)
- if (!isInCache(orgId + classId)){
- if('數(shù)據(jù)庫的重復判斷'){
-
- }
- }
以上代碼就是對重復插入的判斷,外層判斷使線程不重復,內(nèi)層判斷使數(shù)據(jù)不重復。
- synchronized (Cache.getInstance()) {
- Cache.getInstance().remove(orgId + classId);
- }
最后,代碼執(zhí)行完后,請釋放資源,告訴其他相同想法的線程可以進來看一下,但是由于你已經(jīng)執(zhí)行了,數(shù)據(jù)庫已經(jīng)有值了,內(nèi)層判斷不會讓它執(zhí)行的。當然你也可以不釋放,最后的結果是Map里面的值越積越多~~