天天看點

單例模式——java設計模式

單例模式

目錄:

一、何為單例

二、使用Java EE實作單例模式

三、使用場景

確定一個類隻有一個執行個體,并且提供了執行個體的一個全局通路點

**1.1 單例模式類圖 **

              

1.2 單例模式實作

(1)簡單實作

public class MySingleton1 {
	private static MySingleton1 instance;

	private MySingleton1() {
	}

	public static MySingleton1 getInstance() {
		if (instance == null) { // 1
			instance = new MySingleton1();
		}
		return instance;
	}
}
           

(2)線程安全的單例模式

要解決競态條件問題,你就需要獲得一把鎖,并且在執行個體傳回後才釋放。

public class MySingleton2 {

	private static MySingleton2 instance;

	private MySingleton2() {
	}

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

(3)類加載時建立單例對象

這樣不必同步單例執行個體的建立,并在JVM加載完所有類時就建立好單例對象

public class MySingleton3 {

	private final static MySingleton3 instance = new MySingleton3();

	private MySingleton3() {
	}

	public static MySingleton3 getInstance() {
		return instance;
	}
}
           

(4)靜态塊中的單例

這會導緻延遲初始化,因為靜态塊是在構造方法調用前執行的。

public class MySingleton4 {
	private static MySingleton4 instance = null;
	static {
		instance = new MySingleton4();
	}
	private MySingleton4() {
	}
	public static MySingleton4 getInstance() {
		return instance;
	}
}
           

(5)雙重檢測鎖

雙重檢測鎖比其他方法更加安全,因為它會在鎖定單例類之前檢查一次單例的建立,在對象創造前再一次檢查

public class MySingleton6 {

	private volatile MySingleton6 instance;

	private MySingleton6() {
	}

	public MySingleton6 getInstance() {
		if (instance == null) { // 1
			synchronized (MySingleton6.class) {
				if (instance == null) { // 2
					instance = new MySingleton6();
				}
			}
		}
		return instance;
	}
}
           

(6)枚舉類型的單例模式

      上面的方法都不是絕對安全的,如果開發者講Java Reflection API的通路修飾符改為public,就可以建立單例了。Java中建立單例最佳方式是枚舉類型。

      枚舉類型本質上就是單例的,是以JVM會處理建立單例所需的大部分工作。這樣,通過使用枚舉類型,就無需再處理同步對象建立與提供等工作了,還能避免與初始化相關的問題。

public enum MySingletonEnum {
	INSTANCE;
	public void doSomethingInteresting() {
	}
}
           

在該示例中,對單例對象示例的引用是通過以下方式獲得的:

MySingletonEnum mse=MySingletonEnum.INSTANCE;

一旦擁有了單例的引用,你就可以向下面這樣調用它的任何方法了:

mse.doSomeThingInteresting();

Java EE中可以使用上面的方法,但是還有一種更加優雅且易于使用的方式:單例Bean

1.單例Bean

隻需将注解@Singleton添加到類上就可以将其轉換為單例Bean

import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import java.util.logging.Logger;

@Singleton
public class CacheSingletonBean8 {

	private Map<Integer, String> myCache;

	@PostConstruct
	public void start() {
		Logger.getLogger("MyGlobalLogger").info("Started!");
		myCache = new HashMap<Integer, String>();
	}

	public void addUser(Integer id, String name) {
		myCache.put(id, name);
	}

	public String getName(Integer id) {
		return myCache.get(id);
	}
}
           

      通過注解的簡單使用,Java EE不必配置XML檔案。項目中有一個beans.xml檔案,不過大多數時候其内容都是空的。你隻是在啟動上下文與依賴注入(CDI)容器時才需要使用它。@Singleton注解将類标記為一個單例EJB,容器會處理該單例執行個體的建立與使用。

2.在啟動時使用單例

預設情況下,Java EE的單例是延遲初始化的,隻在需要執行個體時并且是首次通路時才建立它。不過,你可能希望在啟動時就建立執行個體,不需要任何延遲即可通路到單例,特别是建立執行個體的代價很大或是在容器啟動時就需要Bean。要確定建立時就啟動,可在類上使用@Startup注解。

import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import java.util.logging.Logger;

@Startup
@Singleton
public class CacheSingletonBean9 {

	private Map<Integer, String> myCache;

	@PostConstruct
	public void start() {
		Logger.getLogger("MyGlobalLogger").info("Started!");
		myCache = new HashMap<Integer, String>();
	}

	public void addUser(Integer id, String name) {
		myCache.put(id, name);
	}

	public String getName(Integer id) {
		return myCache.get(id);
	}
}
           

3.确定啟動順序

但是,如果單例依賴于其他資源怎麼辦?比如:如果連接配接池是由另一個單例建立的會怎麼樣,或者日志依賴于另一個單例呢?Java EE提供了一個簡單的注解來解決這個問題。使用@DependsOn注解,并将該類是以來的Bean的名字傳遞給它。

import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.DependsOn;
import javax.ejb.EJB;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Singleton;
import javax.ejb.Startup;

@Startup
@DependsOn("MyLoggingBean")  //加上此注解
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@Singleton
public class CacheSingletonBean12 {

	private Map<Integer, String> myCache;

	@EJB
	MyLoggingBean loggingBean;

	@PostConstruct
	public void start() {
		loggingBean.logInfo("Started!");
		myCache = new HashMap<Integer, String>();
	}

	@Lock(LockType.WRITE)
	public void addUser(Integer id, String name) {
		myCache.put(id, name);
	}

	@Lock(LockType.READ)
	public String getName(Integer id) {
		return myCache.get(id);
	}
}
           

接下來再建立一個單例Bean,作為上一個Bean所用的Bean

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import java.util.logging.Logger;

@Startup
@Singleton
public class MyLoggingBean {

	private Logger logger;

	@PostConstruct
	public void start() {
		logger = Logger.getLogger("MyGlobalLogger");
		logger.info("Well, I started first!!!");
	}

	public void logInfo(String msg) {
		logger.info(msg);
	}
}
           

4.管理并發

Java Ee提供了兩種并發管理:容器管理并發與Bean管理并發。在容器管理并發中,容器負責處理讀寫通路相關的一切事宜,而Bean管理并發則需要開發者使用同步等傳統的Java方法來處理并發。

可以通過ConcurrencyManagementType.BEAN注解管理并發。

預設情況下,Java EE使用的事容器管理并發,不過可以通過ConcurrentManagementType.CONTAINER注解進行顯示聲明。

@Startup
@DependsOn("MyLoggingBean")
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@Singleton
@AccessTimeout(value = 120000)
// default in milliseconds
public class CacheSingletonBean13 {
           

回到之前的示例,使用@Lock注解來控制通路

import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.DependsOn;
import javax.ejb.EJB;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Singleton;
import javax.ejb.Startup;

@Startup
@DependsOn("MyLoggingBean")
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@Singleton
public class CacheSingletonBean12 {

	private Map<Integer, String> myCache;

	@EJB
	MyLoggingBean loggingBean;

	@PostConstruct
	public void start() {
		loggingBean.logInfo("Started!");
		myCache = new HashMap<Integer, String>();
	}

	@Lock(LockType.WRITE)
	public void addUser(Integer id, String name) {
		myCache.put(id, name);
	}

	@Lock(LockType.READ)
	public String getName(Integer id) {
		return myCache.get(id);
	}
}
           

三、單例模式的使用場景

一般來說,大量使用單例可能是一個濫用的信号,你應該在合理情況下使用單例:

  • 跨越整個應用程式域來通路共享資料,比如配置資料
  • 隻加載并緩存代價高傲的資源一次,這樣可以做到全局共享通路并改進性能
  • 建立應用日志執行個體,因為通常情況下隻需要一個即可
  • 管理實作了工廠模式的類中的對象
  • 建立門面對象,因為通常情況下隻需要一個即可
  • 延遲建立靜态類,單例可以做到延遲執行個體化

對于重要的緩存解決方案來說,請考慮使用架構,比如:Ehcache、Java Caching System

參考自:《Java EE設計模式解析與應用》

如果,您認為閱讀這篇部落格讓您有些收獲,不妨拿出手機【微信掃一掃】

單例模式——java設計模式

您的資助是我最大的動力!

金額随意,歡迎來賞!

繼續閱讀