单例模式

目录:

一、何为单例

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

单例模式——Java EE设计模式解析与应用的更多相关文章

  1. Java EE设计模式(主要简单介绍工厂模式,适配器模式和模板方法模式)

    Java EE设计模式分为三种类型,共23种: 创建型模式:单例模式.抽象工厂模式.建造者模式.工厂模式.原型模式. 结构型模式:适配器模式.桥接模式.装饰模式.组合模式.外观模式.享元模式.代理模式 ...

  2. java EE设计模式简介

    1.何为设计模式 设计模式提供了对常见应用设计问题的解决方案.在面向对象的编程中,设计模式通常在解决与对象创建和交互相关的问题,而非整体软件架构所面对的大规模问题,它们以样板代码的形式提供了通用的解决 ...

  3. 单例模式——java设计模式

    单例模式 目录: 一.何为单例 二.使用Java EE实现单例模式 三.使用场景 一.何为单例 确保一个类只有一个实例,并且提供了实例的一个全局访问点 1.1 单例模式类图               ...

  4. java常用设计模式总览

    一.java的设计模式大体上分为三大类: 创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式. 结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组 ...

  5. Java并发编程中的设计模式解析(二)一个单例的七种写法

    Java单例模式是最常见的设计模式之一,广泛应用于各种框架.中间件和应用开发中.单例模式实现起来比较简单,基本是每个Java工程师都能信手拈来的,本文将结合多线程.类的加载等知识,系统地介绍一下单例模 ...

  6. java设计模式解析(11) Chain责任链模式

    设计模式系列文章 java设计模式解析(1) Observer观察者模式 java设计模式解析(2) Proxy代理模式 java设计模式解析(3) Factory工厂模式 java设计模式解析(4) ...

  7. java设计模式解析(1) Observer观察者模式

      设计模式系列文章 java设计模式解析(1) Observer观察者模式 java设计模式解析(2) Proxy代理模式 java设计模式解析(3) Factory工厂模式 java设计模式解析( ...

  8. Java与设计模式之单例模式(下) 安全的单例模式

          关于单例设计模式,<Java与设计模式之单例模式(上)六种实现方式>介绍了6种不同的单例模式,线程安全,本文介绍该如何保证单例模式最核心的作用——“实现该模式的类有且只有一个实 ...

  9. Java与设计模式之单例模式(上)六种实现方式

           阎宏博士在<JAVA与模式>中是这样描述单例模式的:作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类.      ...

随机推荐

  1. struts2(二) 表单参数自动封装和参数类型自动转换

    前篇文章对struts2的一个入门,重点是对struts2的架构图有一个大概的了解即可,之后的几篇文章,就是细化struts2,将struts2中的各种功能进行梳理,其实学完之后,对struts2的使 ...

  2. Linux云自动化运维第三课

    Linux云自动化运维第三课 一.正则表达式 1.匹配符 * ###匹配0到任意字符 ? ###匹配单个字符 [[:alpha:]] ###匹配单个字母 [[:lower:]] ###匹配单个小写字母 ...

  3. WPF之路一:相对路径图片显示

    由于公司项目的需要,改为WPF开发,因此需要学习WPF,遇到的第一个问题就是在显示的图片的时候,写绝对路径,图片显示没有问题,但是写相对路径的时候,发现图片无法正常显示,在网上搜了一下,得到的答案是需 ...

  4. 测试开发Python培训:实现屌丝的黄色图片收藏愿望(小插曲)

    男学员在学习python的自动化过程中对于爬虫很感兴趣,有些学员就想能收藏一些情色图片,供自己欣赏.作为讲师只能是满足愿望,帮助大家实现对美的追求,http://wanimal.lofter.com/ ...

  5. 老李分享: Oracle Performance Tuning Overview 翻译下

    1.2性能调优特性和工具 Effective data collection and analysis isessential for identifying and correcting perfo ...

  6. nginx 配置禁用ip地址访问

    做过面向公网WEB运维的苦逼们肯定见识过各种恶意扫描.拉取.注入等图谋不轨行为吧?对于直接对外的WEB服务器,我们可以直接通过 iptables . Nginx 的deny指令或者是程序来ban掉这些 ...

  7. 静态数据的初始化(Chapter5.7.2)

    先初始化主类中的静态数据,如果要用其他类来定义对象,则初始化对应的其他类. 实例化对象时,先初始化定义为static的数据,接着调用父类的构造函数(如果有父类),再初始化定义为非static的数据,最 ...

  8. 关于苹果真机 getFullYear()返回值为NAN的问题

    问题描述: 在html页面中获得后台传过来的一个时间并显示在页面上,我用getFullYear() ,getMonth(),getDate()分别获得了年月日在电脑上和三星手机上页面都能正确的显示时间 ...

  9. form表单的enctype

    form表单在你不写enctype属性时,也默认为其添加了enctype属性值,默认值是enctype="application/x- www-form-urlencoded".这 ...

  10. linux入门之用户管理

    用户管理 添加用户 以root用户执行 adduser  或 useradd [new_account] -u  UID   -d 指定家目录 -g GID 指定一个基本组ID   -G指定一个附加组 ...