這次和大家分享一下設計模式中的單例模式。
說到單例模式,我相信大家都瞭解,簡單說就是某個類在整個程序中只有一個對象。那爲什麼使用單例模式呢?什麼場景使用單例模式呢?
我個人認爲,在程序設計中,經過分析,某個類有一個對象已經可以滿足要求,如果此時再加上這個類會消耗許多資源(包括內存開銷大,創建對象耗時等),這個類就推薦使用單例來實現了。比如:數據庫連接及相關操作,圖片加載,緩存,線程池等等。使用單例模式也可以更好的實現共享資源。
那如何實現單例模式呢?在這裏我和大家分享五種方式。在這五種方式中,都是以一個簡單的計算器例子來說明,計算加減乘除的類對象明顯在程序中只要有一個就可以了。在這裏只是爲了介紹這些方式實現。
1. 餓漢模式
/**
* 餓漢模式實現單例模式
* 好處:簡單
* 缺點:
* 當類加載的時候就創建了對象而不管對象時候在使用
* 造成資源的浪費
* 當內存不足的時候,回收了靜態對象,此時訪問也會帶來bug
* @author wangpeiyu
*
*/
public class Hungry {
private static Hungry instance= new Hungry();
private Hungry(){
}
public Hungry getInstance(){
return instance;
}
public int add(int a,int b){
return a+b;
}
public int sub(int a,int b){
return a-b;
}
public int mul(int a, int b){
return a*b;
}
public float div(int a,int b)
{
return a/b;
}
}
2. 懶漢模式
/**
* 懶漢實現單例模式
* 好處:解決了線程安全,也做到了要使用的時候才創建對象
* 缺點:每次要獲取該對象的時候,都要進行線程同步,即使在instance已經賦值了之後
* 還要進行線程同步,這就造成了不必要的時間浪費和資源浪費
* @author wangpeiyu
*
*/
public class Lazy {
private static Lazy instance=null;
private Lazy(){}
public synchronized static Lazy getInstance(){
if(instance==null){
instance = new Lazy();
}
return instance;
}
public int add(int a,int b){
return a+b;
}
public int sub(int a,int b){
return a-b;
}
public int mul(int a, int b){
return a*b;
}
public float div(int a,int b)
{
return a/b;
}
}
3. 靜態內部類方式
/**
* 靜態內部類實現單例模式
* 好處:線程安全、也達到了延遲加載、當加載單例類的時候並沒有創建對象。
* 缺點:內存不足,回收靜態對象時,在訪問會出現bug
* 無法解決發序列化導致的對象重建
* @author wangpeiyu
*
*/
public class StaticInClass {
private StaticInClass(){
}
public static StaticInClass getInstance()
{
return SingletonHolder.instance;
}
/**
* 靜態內部類
* 主要的單例類的holder
* @author wangpeiyu
*
*/
private static final class SingletonHolder{
private static final StaticInClass instance = new StaticInClass();
}
public int add(int a,int b){
return a+b;
}
public int sub(int a,int b){
return a-b;
}
public int mul(int a, int b){
return a*b;
}
public float div(int a,int b)
{
return a/b;
}
}
4. 雙重檢查加鎖方式
/**
* 雙重檢查加鎖模式實現單例模式
* 好處:延遲了加載、線程安全,而且也避免了在instance已經賦值的情況下,多線程還要
* 同步造成的資源浪費和無意義的等待
* 缺點:當反序列化時無法避免會重新創建對象的問題
* @author wangpeiyu
*
*/
public class DCL {
private static DCL instance = null;
private DCL(){}
public static DCL getInstance(){
if(instance==null){
synchronized (DCL.class) {
if(instance==null)
{
instance = new DCL();
}
}
}
return instance;
}
public int add(int a,int b){
return a+b;
}
public int sub(int a,int b){
return a-b;
}
public int mul(int a, int b){
return a*b;
}
public float div(int a,int b)
{
return a/b;
}
}
5. 枚舉方法
/**
* 使用枚舉實現單例
* 好處:
* 1.完全確保整個程序只有一個實例
* 2.線程安全,因爲枚舉一旦定義結束,編譯器就不允許我們再使用其構造器來創建任何實例了
* 3.構造函數是private的,即使定義爲public,一樣爲private
* 4.自由序列化,這樣即使在反序列化時也不會重新創建
* @author wangpeiyu
*
*/
public enum EnumCaculate {
instance;
private EnumCaculate(){
}
public int add(int a,int b){
return a+b;
}
public int sub(int a,int b){
return a-b;
}
public int mul(int a, int b){
return a*b;
}
public float div(int a,int b)
{
return a/b;
}
}
在這五種實現中,我個人比較推薦枚舉實現和雙重檢查加鎖實現。原因在每個類的代碼中體現了。