• 单例模式——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实现单例模式

    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设计模式解析与应用》

  • 相关阅读:
    克服 iOS HTML5 音频的局限
    oracle__删除重复记录__三种方法及总结(转载百度文库)
    Oracle 字符集
    无法通过网页进入em
    Ubuntu 12.04(32位)安装Oracle 11g(32位)全过程以及几乎所有问题的解决办法
    正则表达式30分钟入门教程
    linux下安装jdk
    QTP相关书籍
    假的数论gcd,真的记忆化搜索(Codeforce 1070- A. Find a Number)
    搜索基础_HDU1312_dfs_递归+stack实现+bfs_queue实现
  • 原文地址:https://www.cnblogs.com/w1570631036/p/6812730.html
Copyright © 2020-2023  润新知