EF 提交提示System.Data.Entity.Infrastructure.DbUpdateException異常的解決方法
阿新 • • 發佈:2020-11-19
設計模式之單例模式
單例模式(singleton)是設計模式中最簡單的一種模式之一,它作用於單個類,且該類只有一個例項物件。
單例模式的實現有很多種:懶漢,餓漢,加鎖,雙重加鎖等,接下來我將一一說明每種方式的實現。
餓漢模式:
/** * 餓漢式:不管你是否需要我這個例項,在我類載入的時候都給你初始化出來。 * 類載入到記憶體後,就例項化一個單例,JVM保證執行緒安全 * 簡單實用,推薦使用! * 唯一缺點:不管用到與否,類裝載時就完成例項化 */ public class Mgr01 { private static final Mgr01 INSTANCE = new Mgr01();
//將構造方法私有化,其他類無法呼叫該類的構造方法無法對其進行初始化。
private Mgr01() {} public static Mgr01 getInstance() { return INSTANCE; } //進行驗證,判斷m1,m2是否為同一個物件 public static void main(String[] args) { Mgr01 m1 = Mgr01.getInstance(); Mgr01 m2 = Mgr01.getInstance(); System.out.println(m1 == m2); } }
懶漢模式
/** * lazy loading * 也稱懶漢式 * 在需要例項時,才進行初始化。 * 雖然達到了按需初始化的目的,但卻帶來一些的問題*/ public class Mgr03 { private static Mgr03 INSTANCE; private Mgr03() { } public static Mgr03 getInstance() { //有可能執行緒A,B同時進行了判斷,均為null,然後執行緒A,B都進行了初始化的操作,這樣就生成了兩個例項,與單例模式不符。 if (INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE= new Mgr03(); } return INSTANCE; } //驗證,一個類中的同一個物件的hashcode是相同的, public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()-> System.out.println(Mgr03.getInstance().hashCode()) ).start(); } } }
這是Mgr03的執行結果,可以看出這種寫法會造成例項物件的不一致,不符合單例模式的要求。
懶漢模式不安全,對方法進行加鎖可避免這個問題
/** * lazy loading * 也稱懶漢式 * 雖然達到了按需初始化的目的,但卻帶來執行緒不安全的問題 * 可以通過synchronized解決,但也帶來效率下降 */ public class Mgr04 { private static Mgr04 INSTANCE; private Mgr04() { } public static synchronized Mgr04 getInstance() { if (INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Mgr04(); } return INSTANCE; } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr04.getInstance().hashCode()); }).start(); } } }
結果如圖:
通過給getInstance方法進行synchronized的方法來實現執行緒安全,但是效能方法會被大打折扣。
這時候有人想讓鎖的粒度更細一些,來增加效能,程式碼如下:
/** * lazy loading * 也稱懶漢式 * 雖然達到了按需初始化的目的,但卻帶來執行緒不安全的問題 * 可以通過synchronized解決,但也帶來效率下降 */ public class Mgr05 { private static Mgr05 INSTANCE; private Mgr05() { } public static Mgr05 getInstance() { if (INSTANCE == null) { //妄圖通過減小同步程式碼塊的方式提高效率,然而會出現和不加鎖的懶漢一樣問題 //執行緒A,B同時判斷了INSTANCE為null,A獲得了鎖,new了一個出來,B等待A釋放鎖之後又new了一個出來。 synchronized (Mgr05.class) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Mgr05(); } } return INSTANCE; } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr05.getInstance().hashCode()); }).start(); } } }
這種減少同步程式碼塊的方法,可以找出來問題,就是A,B執行緒有可能同時判斷為null,A獲得鎖建立完物件後,由於B已經判斷過INSTANCE為null,所以當B獲得鎖之後直接建立物件,造成了執行緒安全問題。既然已經知道了原因,所以在獲得鎖之後再加上一層判斷即可,程式碼如下:
/** * lazy loading * 也稱懶漢式 * 通過雙重判斷來解決執行緒安全問題。 */ public class Mgr06 {
//這裡加上valatile是為了防止指令重排, private static volatile Mgr06 INSTANCE; //JIT private Mgr06() { } public static Mgr06 getInstance() {
//這裡必須加上判斷,可以減少後面執行緒獲取,判斷不為null,直接往下走,不需要每一件執行緒都去爭取鎖。 if (INSTANCE == null) { //雙重檢查 synchronized (Mgr06.class) { if(INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Mgr06(); } } } return INSTANCE; } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr06.getInstance().hashCode()); }).start(); } } }
還有兩種比較少見的方法:靜態內部類的方式和列舉的方式。
靜態內部類
/** * 靜態內部類方式 * JVM保證單例 * 載入外部類時不會載入內部類,只有呼叫getInstance方法時,才會進行載入。這樣可以實現懶載入 */ public class Mgr07 { private Mgr07() { } private static class Mgr07Holder { private final static Mgr07 INSTANCE = new Mgr07(); } public static Mgr07 getInstance() { return Mgr07Holder.INSTANCE; } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr07.getInstance().hashCode()); }).start(); } } }
這個方法的執行緒安全是由JVM保證的。
列舉的方法:
/** * 不僅可以解決執行緒同步,還可以防止反序列化。 */ public enum Mgr08 { INSTANCE; public void m() {} public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr08.INSTANCE.hashCode()); }).start(); } }
這是最牛逼的方法,也是最簡單的方法。是《Effective Java》的作者書中說明的一種實現單例的方法。
總結
- 單例模式實現的思想就是構造方法私有化。
- 實際開發中餓漢模式就足以滿足。不需要很複雜的二重判斷,做到心中有劍,手中無劍。
- 列舉是最好的實現單例的方式。
- 使用雙重檢查實現單例時,必須交上volatile關鍵字,防止指令重排。