博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
单例模式——java设计模式
阅读量:5117 次
发布时间:2019-06-13

本文共 6680 字,大约阅读时间需要 22 分钟。

单例模式

目录:

一、何为单例
二、使用Java EE实现单例模式
三、使用场景

一、何为单例

确保一个类只有一个实例,并且提供了实例的一个全局访问点

1.1 单例模式类图
              QQ%E5%9B%BE%E7%89%8720170505130542.png
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;@Singletonpublic class CacheSingletonBean8 {    private Map
myCache; @PostConstruct public void start() { Logger.getLogger("MyGlobalLogger").info("Started!"); myCache = new HashMap
(); } 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@Singletonpublic class CacheSingletonBean9 {    private Map
myCache; @PostConstruct public void start() { Logger.getLogger("MyGlobalLogger").info("Started!"); myCache = new HashMap
(); } 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)@Singletonpublic class CacheSingletonBean12 {    private Map
myCache; @EJB MyLoggingBean loggingBean; @PostConstruct public void start() { loggingBean.logInfo("Started!"); myCache = new HashMap
(); } @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@Singletonpublic 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 millisecondspublic 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)@Singletonpublic class CacheSingletonBean12 {    private Map
myCache; @EJB MyLoggingBean loggingBean; @PostConstruct public void start() { loggingBean.logInfo("Started!"); myCache = new HashMap
(); } @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设计模式解析与应用》

转载于:https://www.cnblogs.com/w1570631036/p/6812730.html

你可能感兴趣的文章
51nod 1428 活动安排问题 (贪心+优先队列)
查看>>
中国烧鹅系列:利用烧鹅自动执行SD卡上的自定义程序(含视频)
查看>>
Solaris11修改主机名
查看>>
latex for wordpress(一)
查看>>
如何在maven工程中加载oracle驱动
查看>>
Flask 系列之 SQLAlchemy
查看>>
aboutMe
查看>>
【Debug】IAR在线调试时报错,Warning: Stack pointer is setup to incorrect alignmentStack,芯片使用STM32F103ZET6...
查看>>
一句话说清分布式锁,进程锁,线程锁
查看>>
python常用函数
查看>>
FastDFS使用
查看>>
服务器解析请求的基本原理
查看>>
[HDU3683 Gomoku]
查看>>
【工具相关】iOS-Reveal的使用
查看>>
数据库3
查看>>
存储分类
查看>>
下一代操作系统与软件
查看>>
【iOS越狱开发】如何将应用打包成.ipa文件
查看>>
[NOIP2013提高组] CODEVS 3287 火车运输(MST+LCA)
查看>>
Yii2 Lesson - 03 Forms in Yii
查看>>