設計模式 - 單例模式(詳解)看看和你理解的是否一樣?

来源:https://www.cnblogs.com/eamonzzz/archive/2019/10/08/11633482.html
-Advertisement-
Play Games

一、概述 單例模式是設計模式中相對簡單且非常常見的一種設計模式,但是同時也是非常經典的 高頻 面試題,相信還是有很多人在面試時會掛在這裡。本篇文章主要針對單例模式做一個回顧,記錄單例模式的應用場景、常見寫法、針對線程安全進行調試(看得見的線程)以及總結。相信大家看完這篇文章之後,對單例模式有一個非常 ...


一、概述

單例模式是設計模式中相對簡單且非常常見的一種設計模式,但是同時也是非常經典的高頻面試題,相信還是有很多人在面試時會掛在這裡。本篇文章主要針對單例模式做一個回顧,記錄單例模式的應用場景、常見寫法、針對線程安全進行調試(看得見的線程)以及總結。相信大家看完這篇文章之後,對單例模式有一個非常深刻的認識。

文章中按照常見的單例模式的寫法,由淺入深進行講解記錄;以及指出該寫法的不足,從而進行演進改造。

秉承廢話少說的原則,我們下麵快速開始

二、定義

單例模式(Singleton Pattern)是指確保一個類在任何情況下都絕對只有一個實例,並提供一個全局訪問點。

單例模式是創建型模式。

三、應用場景

  1. 生活中的單例:例如,國家主席、公司 CEO、部門經理等。
  2. Java世界中:ServletContextServletContextConfig 等;
  3. Spring 框架應用中:ApplicationContext、資料庫的連接池也都是單例形式。

四、常見的單例模式寫法

單例模式主要有:餓漢式單例、懶漢式單例(線程不安全型、線程安全型、雙重檢查鎖類型、靜態內部類類型)、註冊式(登記式)單例(枚舉式單例、容器式單例)、ThreadLocal線程單例

下麵我們來看看各種模式的寫法。

1、餓漢式單例

餓漢式單例是在類載入的時候就立即初始化,並且創建單例對象。絕對線程安全,線上程還沒出現以前就是實例化了,不可能存在訪問安全問題。

Spring 中 IOC 容器 ApplicationContext 就是典型的餓漢式單例

優缺點

優點:沒有加任何的鎖、執行效率比較高,在用戶體驗上來說,比懶漢式更好。

缺點:類載入的時候就初始化,不管用與不用都占著空間,浪費了記憶體,有可能占著茅坑不拉屎。

寫法

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午9:26
 */
public class HungrySingleton {
    // 1.私有化構造器
    private HungrySingleton (){}
    // 2.在類的內部創建自行實例
    private static final HungrySingleton instance = new HungrySingleton();
    // 3.提供獲取唯一實例的方法(全局訪問點)
    public static HungrySingleton getInstance(){
        return instance;
    }
}

還有另外一種寫法,利用靜態代碼塊的機制:

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午10:46
 */
public class HungryStaticSingleton {
    // 1. 私有化構造器
    private HungryStaticSingleton(){}

    // 2. 實例變數
    private static final HungryStaticSingleton instance;

    // 3. 在靜態代碼塊中實例化
    static {
        instance = new HungryStaticSingleton();
    }

    // 4. 提供獲取實例方法
    public static HungryStaticSingleton getInstance(){
        return instance;
    }
}

測試代碼,我們創建 10 個線程(具體線程發令槍 ConcurrentExecutor 在文末源碼中獲取):

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午11:17
 */
public class HungrySingletonTest {
    @Test
    public void test() {
        try {
            ConcurrentExecutor.execute(() -> {
                HungrySingleton instance = HungrySingleton.getInstance();
                System.out.println(Thread.currentThread().getName() + " : " + instance);
            }, 10, 10);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

測試結果:

pool-1-thread-6 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-9 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-10 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-7 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
pool-1-thread-8 : com.eamon.javadesignpatterns.singleton.hungry.HungrySingleton@5e37cce6
...

可以看到,餓漢式每次獲取實例都是同一個。

使用場景

這兩種寫法都非常的簡單,也非常好理解,餓漢式適用在單例對象較少的情況。

下麵我們來看性能更優的寫法——懶漢式單例。


2、懶漢式單例

懶漢式單例的特點是:被外部類調用的時候內部類才會載入。

懶漢式單例可以分為下麵這幾種寫法來。

簡單懶漢式(線程不安全)

這是懶漢式單例的簡單寫法

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午10:55
 */
public class LazySimpleSingleton {
    private LazySimpleSingleton(){}
    private static LazySimpleSingleton instance = null;

    public static LazySimpleSingleton getInstance(){
        if (instance == null) {
            instance = new LazySimpleSingleton();
        }
        return instance;
    }
}

我們創建一個多線程來測試一下,是否線程安全:

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午11:12
 */
public class LazySimpleSingletonTest {

    @Test
    public void test() {
        try {
            ConcurrentExecutor.execute(() -> {
                LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
                System.out.println(Thread.currentThread().getName() + " : " + instance);
            }, 5, 5);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

運行結果:

pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@748e48d0
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.lazy.LazySimpleSingleton@abe194f

從測試結果來看,一定幾率出現創建兩個不同結果的情況,意味著上面的單例存線上程安全隱患。

至於為什麼?由於篇幅問題,我們在後面一篇文章中利用測試工具進行詳細的分析(這可能也是面試中面試官會問到的問題)。大家現在只需要知道簡單的懶漢式會存在這麼一個問題就行了。

簡單懶漢式(線程安全)

通過對上面簡單懶漢式單例的測試,我們知道存線上程安全隱患,那麼,如何來避免或者解決呢?

我們都知道 java 中有一個synchronized可以來對共用資源進行加鎖,保證在同一時刻只能有一個線程拿到該資源,其他線程只能等待。所以,我們對上面的簡單懶漢式進行改造,給getInstance() 方法加上synchronized

/**
 * @author eamon.zhang
 * @date 2019-09-30 上午10:55
 */
public class LazySimpleSyncSingleton {
    private LazySimpleSyncSingleton() {
    }

    private static LazySimpleSyncSingleton instance = null;

    public synchronized static LazySimpleSyncSingleton getInstance() {
        if (instance == null) {
            instance = new LazySimpleSyncSingleton();
        }
        return instance;
    }
}

然後使用發令槍進行測試:

@Test
public void testSync(){
    try {
        ConcurrentExecutor.execute(() -> {
            LazySimpleSyncSingleton instance = LazySimpleSyncSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + " : " + instance);
        }, 5, 5);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

進行多輪測試,並觀察結果,發現能夠獲取同一個示例:

pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.lazy.simple.LazySimpleSyncSingleton@1a7e99de

線程安全問題是解決了,但是,用synchronized加鎖,線上程數量比較多情況下,如果CPU分配壓力上升,會導致大批量線程出現阻塞,從而導致程式運行性能大幅下降。

那麼,有沒有一種更好的方式,既兼顧線程安全又提升程式性能呢?答案是肯定的。

我們來看雙重檢查鎖的單例模式。

雙重檢查鎖懶漢式

上面的線程安全方式的寫法,synchronized鎖是鎖在 getInstance() 方法上,當多個線程過來拿資源的時候,其實需要拿的不是getInstance()這個方法,而是getInstance()方法裡面的instance 實例對象,而如果這個實例對象一旦被初始化之後,多個線程到達時,就可以利用方法中的 if (instance == null) 去判斷是否實例化,如果已經實例化了就直接返回,就沒有必要再進行實例化一遍。所以對上面的代碼進行改造:

第一次改造:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午2:03
 */
public class LazyDoubleCheckSingleton {
    private LazyDoubleCheckSingleton() {
    }

    private static LazyDoubleCheckSingleton instance = null;

    public static LazyDoubleCheckSingleton getInstance() {
        // 這裡判斷是為了過濾不必要的同步加鎖,因為如果已經實例化了,就可以直接返回了
        if (instance == null) {
            // 如果未初始化,則對資源進行上鎖保護,待實例化完成之後進行釋放
            synchronized (LazyDoubleCheckSingleton.class) {
                instance = new LazyDoubleCheckSingleton();
            }
        }
        return instance;
    }
}

這種方法行不行?答案肯定是不行,代碼中雖然是將同步鎖添加到了實例化操作中,解決了每個線程由於同步鎖的原因引起的阻塞,提高了性能;但是,這裡會存在一個問題:

  • 線程X線程Y同時調用getInstance()方法,他們同時判斷instance == null,得出的結果都是為null,所以進入了if代碼塊了
  • 此時線程X得到CPU的控制權 -> 進入同步代碼塊 -> 創建對象 -> 返回對象
  • 線程X執行完成了以後,釋放了鎖,然後線程Y得到了CPU的控制權。同樣是 -> 進入同步代碼塊 -> 創建對象 -> 返回對象

所以我們明顯可以分析出來:LazyDoubleCheckSingleton 類返回了不止一個實例!所以上面的代碼是不行的!大家可以自行測試,我這裡就不進行測試了!

我們再進行改造,經過分析,由於線程X已經實例化了對象,在線程Y再次進入的時候,我們再加一層判斷不就可以解決 “這個” 問題嗎?確實如此,來看代碼:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午2:03
 */
public class LazyDoubleCheckSingleton {
    private LazyDoubleCheckSingleton() {
    }
    private static LazyDoubleCheckSingleton instance = null;
    public static LazyDoubleCheckSingleton getInstance() {
        // 這裡判斷是為了過濾不必要的同步加鎖,因為如果已經實例化了,就可以直接返回了
        if (instance == null) {
            // 如果未初始化,則對資源進行上鎖保護,待實例化完成之後進行釋放(註意,可能多個線程會同時進入)
            synchronized (LazyDoubleCheckSingleton.class) {
                // 這裡的if作用是:如果後面的進程在前面一個線程實例化完成之後拿到鎖,進入這個代碼塊,
                // 顯然,資源已經被實例化過了,所以需要進行判斷過濾
                if (instance == null) {
                    instance = new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

大家覺得經過這樣改造是不是就完美了呢?在我們習慣性的“講道理”的思維模式看來,好像確實沒什麼問題,但是,程式是電腦在執行;什麼意思呢?

instance = new LazyDoubleCheckSingleton(); 這段代碼執行的時候,電腦內部並非簡單的一步操作,也就是非原子操作,在JVM中,這一行代碼大概做了這麼幾件事情:

  1. instance 分配記憶體
  2. 調用 LazyDoubleCheckSingleton 的構造函數來初始化成員變數
  3. instance對象指向分配的記憶體空間(執行完這步 instance 就為非 null 了)

但是在 JVM 中的即時編譯器中存在指令重排序的優化;通俗的來說就是,上面的第二步和第三步的順序是不能保證的,如果執行順序是 1 -> 3 -> 2 那麼在 3 執行完畢、2 未執行之前,被另外一個線程 A 搶占了,這時 instance 已經是非 null 了(但卻沒有初始化),所以線程 A 會直接返回 instance,然後被程式調用,就會報錯。

當然,這種情況是很難測試出來的,但是確實會存在這麼一個問題,所以我們必須解決它,解決方式也很簡單,就是 j 將 instance 加上 volatile 關鍵字。

所以相對較完美的實現方式是:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午2:03
 */
public class LazyDoubleCheckSingleton {
    private LazyDoubleCheckSingleton() {
    }

    private static volatile LazyDoubleCheckSingleton instance = null;

    public static LazyDoubleCheckSingleton getInstance() {
        // 這裡判斷是為了過濾不必要的同步加鎖,因為如果已經實例化了,就可以直接返回了
        if (instance == null) {
            // 如果未初始化,則對資源進行上鎖保護,待實例化完成之後進行釋放(註意,可能多個線程會同時進入)
            synchronized (LazyDoubleCheckSingleton.class) {
                // 這裡的if作用是:如果後面的進程在前面一個線程實例化完成之後拿到鎖,進入這個代碼塊,
                // 顯然,資源已經被實例化過了,所以需要進行判斷過濾
                if (instance == null) {
                    instance = new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

測試代碼見文末說明

靜態內部類懶漢式

上面的雙重鎖檢查形式的單例,對於日常開發來說,確實夠用了,但是在代碼中使用synchronized關鍵字 ,總歸是要上鎖,上鎖就會存在一個性能問題。難道就沒有更好的方案嗎?別說,還真有,我們從類初始化的角度來考慮,這就是這裡所要說到的靜態內部類的方式。

廢話不多說,直接看代碼:

/**
 *
 * @author eamon.zhang
 * @date 2019-09-30 下午2:55
 */
public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton() {
    }

    // 註意關鍵字final,保證方法不被重寫和重載
    public static final LazyInnerClassSingleton getInstance() {
        return LazyHolder.INSTANCE;
    }

    private static class LazyHolder {
        // 註意 final 關鍵字(保證不被修改)
        private static final LazyInnerClassSingleton INSTANCE = new LazyInnerClassSingleton();
    }
}

進行多線程測試:

pool-1-thread-9 : com.eamon.javadesignpatterns.singleton.lazy.inner.LazyInnerClassSingleton@88b7fa2
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.lazy.inner.LazyInnerClassSingleton@88b7fa2
pool-1-thread-6 : com.eamon.javadesignpatterns.singleton.lazy.inner.LazyInnerClassSingleton@88b7fa2
...

結果都是同一個對象實例。

結論

這種方式即解決了餓漢式的記憶體浪費問題,也解決了synchronized 所帶來的性能問題

原理

利用的原理就是類的載入初始化順序:

  1. 當類不被調用的時候,類的靜態內部類是不會進行初始化的,這就避免了記憶體浪費問題;
  2. 當有方法調用 getInstance()方法時,會先初始化靜態內部類,而靜態內部類中的成員變數是 final 的,所以即便是多線程,其成員變數是不會被修改的,所以就解決了添加 synchronized 所帶來的性能問題

首先感謝也恭喜大家能夠看到這裡,因為我想告訴你,上面所有的單例模式似乎還存在一點小問題 —— 暴力破壞。解決這一問題的方式就是下麵提到的枚舉類型單例。

至於緣由和為何枚舉能夠解決這個問題,同樣,篇幅原因,我將在後面單獨開一篇文章來說明。


下麵我們先來講講註冊式單例。

3、註冊式(登記式)單例

註冊式單例又稱為登記式單例,就是將每一個實例都登記到某一個地方,使用唯一的標識獲取實例。

註冊式單例有兩種寫法:一種為容器緩存,一種為枚舉登記。

先來看枚舉式單例的寫法。

枚舉單例

廢話少說,直接看代碼,我們先創建EnumResource 類:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午3:53
 */
public class EnumResource {
}

然後創建EnumSingleton:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午3:42
 */
public enum EnumSingleton {
    INSTANCE;

    private Object instance;

    EnumSingleton() {
        instance = new EnumResource();
    }

    public Object getInstance() {
        return instance;
    }

}

來看測試代碼:

/**
 * @author eamon.zhang
 * @date 2019-09-30 下午3:47
 */
public class EnumSingletonTest {

    @Test
    public void test() {
        try {
            ConcurrentExecutor.execute(() -> {
                EnumSingleton instance = EnumSingleton.INSTANCE;
                System.out.println(instance.getInstance());
            }, 10, 10);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

測試結果:

com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7
com.eamon.javadesignpatterns.singleton.enums.EnumResource@3eadb1e7

結果都一樣,說明枚舉類單例是線程安全的,且是不可破壞的;在 JDK 枚舉的語法特殊性,以及反射也為枚舉保駕護航,讓枚舉式單例成為一種比較優雅的實現。

枚舉類單例也是《Effective Java》中所建議使用的。

容器式單例

註冊式單例還有另外一種寫法,利用容器緩存,直接來看代碼:

創建ContainerSingleton類:

/**
 * @author EamonZzz
 * @date 2019-10-06 18:28
 */
public class ContainerSingleton {
    private ContainerSingleton() {
    }

    private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();

    public static Object getBean(String className) {
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                Object object = null;
                try {
                    object = Class.forName(className).newInstance();
                    ioc.put(className, object);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return object;
            } else {
                return ioc.get(className);
            }
        }
    }
}

測試代碼:

@Test
    public void test() {
        try {
            ConcurrentExecutor.execute(() -> {
                Object bean = ContainerSingleton
                        .getBean("com.eamon.javadesignpatterns.singleton.container.Resource");
                System.out.println(bean);
            }, 5, 5);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

測試結果:

com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f
com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f
com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f
com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f
com.eamon.javadesignpatterns.singleton.container.Resource@42e7420f

容器式寫法適用於創建實例非常多的情況,便於管理。但是,是非線程安全的。

其實 Spring 中也有相關容器史丹利的實現代碼,比如 AbstractAutowireCapableBeanFactory 介面

至此,註冊式單例介紹完畢。


五、拓展

ThreadLocal 線程單例

ThreadLocal 不能保證其創建的對象是唯一的,但是能保證在單個線程中是唯一的,並且在單個線程中是天生的線程安全。

看代碼:

/**
 * @author EamonZzz
 * @date 2019-10-06 21:40
 */
public class ThreadLocalSingleton {
    private ThreadLocalSingleton() {
    }

    private static final ThreadLocal<ThreadLocalSingleton> instance = ThreadLocal.withInitial(ThreadLocalSingleton::new);

    public static ThreadLocalSingleton getInstance() {
        return instance.get();
    }
}

測試程式:

@Test
public void test() {
    System.out.println("-------------- 單線程 start ---------");
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println(ThreadLocalSingleton.getInstance());
    System.out.println("-------------- 單線程 end ---------");
    System.out.println("-------------- 多線程 start ---------");
    try {
        ConcurrentExecutor.execute(() -> {
            ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + " : " + singleton);

        }, 5, 5);
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("-------------- 多線程 end ---------");
}

測試結果:

-------------- 單線程 start ---------
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@1374fbda
-------------- 單線程 end ---------
-------------- 多線程 start ---------
pool-1-thread-5 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@2f540d92
pool-1-thread-1 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@3ef7ab4e
pool-1-thread-2 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@604ffe2a
pool-1-thread-3 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@50f41c9f
pool-1-thread-4 : com.eamon.javadesignpatterns.singleton.threadlocal.ThreadLocalSingleton@40821a7a
-------------- 多線程 end ---------

從測試結果來看,我們不難發現,在主線程中無論調用多少次,獲得到的實例都是同一個;在多線程環境下,每個線程獲取到了不同的實例。

所以,在單線程環境中,ThreadLocal 可以達到單例的目的。這實際上是以空間換時間來實現線程間隔離的。

六、總結

單例模式可以保證記憶體里只有一個實例,減少了記憶體的開銷;可避免對資源的浪費。

單例模式看起來非常簡單,實現起來也不難,但是在面試中卻是一個高頻的面試題。希望大家能夠徹底理解。


本篇文章所涉及的源代碼:

github.com/eamonzzz


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文僅限於入門級,沒有成規模製作,希望能對你有所幫助。 因為在開發多個項目中可能會用到同一個組件,那麼我們通過複製粘貼的形式更新,無異於是笨拙的,我們可以通過上傳到npm後,不斷迭代npm包來實現更新。 前期準備 初始化project 這裡我們使用 來初始化一個vue項目。 or 首先我們來開發一個 ...
  • 垃圾分類,一般是指按一定規定或標准將垃圾分類儲存、分類投放和分類搬運,從而轉變成公共資源的一系列活動的總稱。分類的目的是提高垃圾的資源價值和經濟價值,力爭物盡其用。垃圾在分類儲存階段屬於公眾的私有品,垃圾經公眾分類投放後成為公眾所在小區或社區的區域性準公共資源,垃圾分類搬運到垃圾集中點或轉運站後成... ...
  • 一.開通QQ服務 "點我進入QQ推廣官網" 然後點擊 後面自己看中文 二.頁面a標簽 三.js實現 ...
  • 什麼是微服務?為什麼會有微服務?讓我們帶著這些疑問開始我們的探索。 我們先看下維基百科和百度百科給出的定義: 維基百科:2014年,Martin Fowler 與 James Lewis 共同提出了微服務的概念,定義了微服務是由以單一應用程式構成的小服務,自己擁有自己的行程與輕量化處理,服務依業務功 ...
  • 0--前言 spring cloud的服務註冊中心,該選擇誰?在選擇前,我們首先需要來瞭解下分散式的CAP定理: 所謂CAP,是指: Consistency:一致性;就是在分散式系統中的所有數據備份,在同一時刻是否同樣的值 Availability:可用性;就是負載過大後,集群整體是否還能響應客戶端 ...
  • 淘寶架構 我們以淘寶架構為例,瞭解下大型的電商項目的服務端的架構是怎樣,如圖所示 上面是一些安全體繫系統,如數據安全體系、應用安全體系、前端安全體系等。 中間是業務運營服務系統,如會員服務、商品服務、店鋪服務、交易服務等。 還有共用業務,如分散式數據層、數據分析服務、配置服務、數據搜索服務等。 最下 ...
  • 策略模式: 1、定義:定義了一系列演算法,並將每個演算法封裝起來,使它們可以相互替換,且演算法的變化不會影響使用演算法的客戶 2、模型結構: (1)抽象策略(Strategy)類:定義了一個公共介面,各種不同的演算法以不同的方式實現這個介面, 環境角色使用這個介面調用不同的演算法,一般使用介面或抽象類實現 (2 ...
  • 迭代器模式(Iterator): 迭代器模式允許你訪問一個數據項序列中的所有元素,而無須關心序列是什麼類型(數組、鏈表、列表或任何其他類型)。它能有效地構建一個數據管道,經過一系列不同的轉換或過濾後再從管道的另一端出來。迭代器模式就是提供一種遍歷集合元素的統一介面,用一致的方法遍歷集合元素,不需要知 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...