1. 程式人生 > 實用技巧 >EF 提交提示System.Data.Entity.Infrastructure.DbUpdateException異常的解決方法

EF 提交提示System.Data.Entity.Infrastructure.DbUpdateException異常的解決方法

設計模式之單例模式

單例模式(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》的作者書中說明的一種實現單例的方法。

總結

  1. 單例模式實現的思想就是構造方法私有化。
  2. 實際開發中餓漢模式就足以滿足。不需要很複雜的二重判斷,做到心中有劍,手中無劍。
  3. 列舉是最好的實現單例的方式。
  4. 使用雙重檢查實現單例時,必須交上volatile關鍵字,防止指令重排。