第二章 单列模式

1.何为单列模式

Spring等企业框架大量使用了单列模式,单列类可以保证其类型只会生成一个实例。只拥有一个实例在很多时候是很有用的,比如说全局访问以及缓存代价高昂的资源。
在JavaEE中提供了一种内建机制,开发者可以通过为类添加注解来方便的创建单列。

1.1单列模式的运用场景

1.跨越整个应用程序域来访问共享数据,比如配置数据
2.只加载并缓存代价高昂的资源一次,这样可以做到全局共享访问并且改进性能。
3.创建应用日志实例,因为通常情况下只需要一个即可。
4.管理实现工厂模式的类中的对象。
5,创建门面对象,因为通常情况下只需要一个即可。
6.延时创建静态类,单列可以做到延迟实例化

1.2单列模式的弊病

1.过度使用单列模式意味着不必要的资源缓存,无法让垃圾收集器回收对象并释放宝贵的内存资源
2.无法利用对象创建和继承的好处,且对单元测试不太友好。

1.3用普通代码实现单列模式

单列模式基于一个单列类,它持有一个指向自身唯一实例的引用,同时通过唯一的getter方法控制其创建与访问。

//单列模式的实现
public class MySingleton1 {
	private static MySingleton1 instance;

	private MySingleton1() {
	}

	public static MySingleton1 getInstance() {
		if (instance == null) { // 1
			instance = new MySingleton1();
		}
		return instance;
	}
}

上面的代码看起来是可以运行的,其实有bug。由于对象创建方法部署原子的,因此在j竞态条件下是很容易出错的。在多线程的环境下,这会导致创建多个单列实例的后果。

解决这一个问题,可以通过synchronized关键字实现锁机制。

public class MySingleton2 {

	private static MySingleton2 instance;

	private MySingleton2() {
	}

	public static synchronized MySingleton2 getInstance() {
		if (instance == null) {
			instance = new MySingleton2();
		}
		return instance;
	}
}

另外一种手段是在加载类的同时创建单列实例,这样就不必同步单列实例的创建,并在JVM加载完所有类时就创建好单列对象(因此,这是在类调用getInstance方法之前发生的)。这所以可以这样,是因为静态成员与静态块是类加载时执行的。

public class MySingleton3 {

	private final static MySingleton3 instance = new MySingleton3();

	private MySingleton3() {
	}

	public static MySingleton3 getInstance() {
		return instance;
	}
}

public class MySingleton4 {

	private static MySingleton4 instance = null;

	static {
		instance = new MySingleton4();
	}

	private MySingleton4() {
	}

	public static MySingleton4 getInstance() {
		return instance;
	}
}

双重检测锁,它比其它方法更加安全,因为它会在锁定单列类之前检查一次单列的创建,在对象创建前再检查一次检查。

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;
	}
}

volatile
synchronized

但其实上面的方法也都不是绝对安全的,在Java中,最佳创建单列的方式是使用枚举类。
枚举类型本质上就是单列的,因此JVM会处理创建单列所需的大部分工作。这样无需再处理同步对象创建和提供工作了,还能避免初始化相关的问题。

public enum MySingletonEnum {
	INSTANCE;
	public void doSomethingInteresting() {
	}
}

枚举类

2.使用JAVA EE实现单列模式

2.1单列Bean

1.只需注解@Singleton添加到类上就可以将其为单列Bean
2.其将类标记为一个单列EJB,容器会处理该单列的创建和使用
3.如果再服务器上执行该EJB,那么你是不会看到来自单列的日志输出的,这是因为带有注解@PostConstruct的方法并没有被调用。

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);
	}
}

@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

从Java EE5规范开始,Servlet中增加了两个影响Servlet生命周期的注解,@PostConstruct和@PreDestroy,这两个注解被用来修饰一个非静态的void()方法。

在默认情况下,javaEE中的单列是延迟初始化的,只在需要实例并且是首次访问时才创建它,要想确保启动时就创建实例,不需要任何延迟即可访问到单列,可在类上加@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);
	}
}

2.2 确定启动顺序

如果上例创建的单列依赖别的资源该怎么办,该如何等待其它资源就绪呢?
可以使用@DependsOn()注解解决这个问题

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);
	}
}
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);
	}
}

一个单列Bean可能会依赖于其他一系列Bean的初始化,这种情况下,可在@DependOn注解中,指定多个Bean

@DependOn({"Bean1","Bean2"})

3.管理并发

Java EE 提供了两种并发管理:容器管理并发与Bean管理并发

在容器并发中,容器负责处理读写访问相关的一切事宜,而bean管理并发则需要开发者使用同步等传统的java方法来处理并发。可以通过ConcurrencyManagementType.BEAN注解进行显式声明

默认情况下,java EE使用的是容器管理并发,不过可以ConcurrencyManagementType.CONTAINER注解进行显式声明。

3.2 使用@locktype注解管理并发

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的业务方法的访问:分别是LockType.WRITE和LockType.READ
对于前者来说,当方法被调用的时候,其它客户端是无法访问bean的;对于后者来说,它允许对方法的并发访问,并且不会对其它客户端锁定bean
还可以在类级别使用LockType,这样会应用到所有没有显示定义的方法上去为Write。

3.3 并发超时访问

mport javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.DependsOn;
import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.AccessTimeout;
import java.util.Map;
import javax.ejb.EJB;
import java.util.HashMap;
import javax.ejb.Lock;
import javax.ejb.LockType;
import java.util.concurrent.TimeUnit;

@Startup
@DependsOn("MyLoggingBean")
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@Singleton
@AccessTimeout(value = 120000)
// default in milliseconds
public class CacheSingletonBean13 {

	private Map<Integer, String> myCache;

	@EJB
	MyLoggingBean loggingBean;

	@PostConstruct
	public void start() {
		loggingBean.logInfo("Started!");
		myCache = new HashMap<Integer, String>();
	}

	@AccessTimeout(value = 30, unit = TimeUnit.SECONDS)
	@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);
	}
}

@AccessTimeout 默认是毫秒单位
还可以在整个类上使用该注解,将其应用到所有没有显示定义的方法上去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值