單例模式
目錄:
一、何為單例
二、使用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設計模式解析與應用》
如果,您認為閱讀這篇部落格讓您有些收獲,不妨拿出手機【微信掃一掃】

您的資助是我最大的動力!
金額随意,歡迎來賞!