1. 程式人生 > >挑戰常規--這樣寫單例是錯的!

挑戰常規--這樣寫單例是錯的!

說到單例,網上教程和很多人信手拈來:

public class Single
{
	private volatile static Single instance;

	private Single()
	{
		System.out.println("建立單例");
	} 
	public static Single getInstance()
	{
		if (instance == null)
		{
			synchronized (Single.class)
			{
				if (instance == null)
				{
					instance = new Single();
				}
			}
		}
		return instance;
	}
}

 自信滿滿,稱之為懶漢載入模式言之節省記憶體,用時才會自動建立。在我看來,這種寫法完全是錯誤的,愚蠢至極,這不是脫褲子放屁,這是脫完褲子再提取褲子再放屁。

正確寫法就是最簡單的:

public class Single
{
	private   static Single instance=new Single();

	private Single()
	{
		System.out.println("建立單例");
	} 
	public static Single getInstance()
	{ 
		return instance;
	}
}

 

下面駁斥所謂的省記憶體。

public class SingleTest
{
 
	public static void main(String[] args) throws  Exception
	{ 
		System.out.println("啟動執行緒");
		System.in.read();
	}

}

 首先,不用單例時,難道別的寫法就會載入單例?沒有引用使用單例類時,當然都不會載入單例。

看圖,並不會載入建立單例,控制檯也不會輸出建立單例。

其次,既然預定要使用單例,那麼都會載入建立一次。

public class SingleTest
{
 
	public static void main(String[] args) throws  Exception
	{ 
		System.out.println("啟動執行緒");
		Single.getInstance();
		System.in.read();
	}

}

 看圖,無論哪種模式單例都會被引用載入

 

是不是用synchronized建立單例就沒有用處了呢?

並不是synchronized是用於解決多執行緒訪問問題帶引數的單例建立才應該使用懶漢模式

因為並不能預期,什麼時候引數被傳入。簡單模式下並不清楚傳入什麼引數,或引數物件未初始化。

 

public class Single
{
	private volatile static Single instance ;   
	public Single(Context context)
	{
		System.out.println("建立單例");
	}
	public static Single getInstance(Context context)
	{
		if (instance == null)
		{
			synchronized (Single.class)
			{
				if (instance == null)
				{
					instance = new Single(context);
				}
			}
		}
		return instance;
	}
}

 

 為什麼說這個是為了解決多執行緒訪問呢,先看如果不加鎖會發生什麼

public class Single
{
    private static Single instance ;   
    public Single(Context context)
    {
        System.out.println("建立單例");
    }
    public static Single getInstance(Context context)
    {
        if (instance == null)
        {
            instance = new Single(context);
        }
        return instance;
    }
}
public class SingleTest
{

	public static void main(String[] args) throws Exception
	{
		ExecutorService pool = Executors.newCachedThreadPool();
		ArrayList<Callable<Void>> runners=new 	ArrayList<>();
		for(int i=0;i<10;i++)
		{
			runners.add(()->{
				Single.getInstance(new Context());
				return null;
			});
		}
		System.out.println("啟動執行緒");
		pool.invokeAll(runners);
		pool.shutdown();
		System.in.read();
	}

}

 不加鎖情況,結果看圖

 

加鎖情況,結果看圖

 

總結,無參構造單例無需複雜的加入synchronized,而未確定的傳參單例需要加synchronized保證多執行緒訪問安全。

思考,如果傳入的引數確定,怎麼寫才最優呢?下面類似寫法是否合理:

 

public class Single
{
	private   static Single instance =new Single(Context.instance);  
 
	public Single(Context context )
	{
		System.out.println("建立單例");
	}
	public static Single getInstance( )
	{
		 
		return instance;
	}
}

 

 

@WebListener
public class MyServletContextListener implements ServletContextListener {
  
    public void contextDestroyed(ServletContextEvent sce)  { 
      
    }
 
    public void contextInitialized(ServletContextEvent sce)  { 
        Single.getInstance(sce);
    }
    
}