单例模式——java设计模式

摘要:
在Java中创建单例的最佳方法是枚举类型。PublicenumMySingletonEnum{INSTANCE;publicvoid doSomethingInterest(){}在本示例中,通过以下方法获得对单例对象示例的引用:MySingleton Enumse=MySinglettonEnum。实例;一旦有了一个实例引用,就可以调用它的任何方法,如下所示:mse。doSomeThingInterest();2、 上面的方法可以用于使用JavaEE实现单例模式,但有一种更优雅、更容易使用的方法:单例Bean1。单例可以通过向类中添加注释@singleton转换为单例Beanimportjava.util.HashMap;导入java.util.Map;importjavax.annotation.PostConstruct;importjavax.ejb.Singleton;importjava.util.loging.Logger;@SingletonpublicclassCacheSingletonBean8{privateMap<Integer,String>myCache;@PostConstructpublicvoidstart(){Logger.getLogger.info(“已启动!
单例模式

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

一、何为单例

确保一个类只有一个实例,并且提供了实例的一个全局访问点
**1.1 单例模式类图 **
              单例模式——java设计模式第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设计模式》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇MySQL ClusterDocker 搭建 etcd 集群下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

SpringBoot启动过程解析(简化)

springBoot web方式启动过程 在这个启动过程中会有各种SpringBoot对外提供的扩展接口来对不同启动阶段进行自定义操作。 了解启动过程也是为了让我们更好理解SpringBoot提供的扩展接口使用   jar包启动或者外置war包启动都是调用SpringApplication.run()方法进行项目启动 tomcat会查询context上下文...

java与json互相转换(解决日期问题)

JSON 即 JavaScript Object Natation,它是一种轻量级的数据交换格式,非常适合于服务器与 JavaScript 的交互。本文主要讲解下java和JSON之间的转换,特别是解决互相转换遇到日期问题的情况。 一、需要相关的jar包: json-lib-xxx.jar ezmorph-xxx.jar c...

mybatis整合ss的时候,无法autowire使用mapper的自动注入,找不到bean:NoSuchBeanDefinitionException

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userDaoImpl': Injection of autowired dependencies failed; nested exception is org.sp...

java 错误 classes路径配置错误

1. 错误显示页 2. 解决步骤 2.1. 查看 root cause 信息 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'urlMapping' defined in ServletContext resource [/WE...

使用Java反射机制将Bean对象转换成Map(驼峰命名方式 — 下划线命名方式)

packagecom.yunping.asap.core.util; importjava.beans.PropertyDescriptor; importjava.lang.reflect.Field; importjava.lang.reflect.Method; importjava.util.ArrayList; importjava.util....

Spring和Spring MVC包扫描

在Spring整体框架的核心概念中,容器是核心思想,就是用来管理Bean的整个生命周期的,而在一个项目中,容器不一定只有一个,Spring中可以包括多个容器,而且容器有上下层关系,目前最常见的一种场景就是在一个项目中引入Spring和SpringMVC这两个框架,那么它其实就是两个容器,Spring是父容器,SpringMVC是其子容器,并且在Spring...