单例模式
单例模式是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。在设计模式中,单例是属于创建型模式,在我们使用的servletContext、servletContextConfig等都是单例的体现形式。单例究竟有多少写法,没有一个明确的说法,更多的是一种思想在单个类上的体现。在这里我大致分类四类: 1、饿汉式 2、懒汉式 3、注册式 4、序列化。下面依次阐述
饿汉式
饿汉式单例在类加载的时候就立即初始化,并且创建单例对象。它是线程安全的,在线程没有出现以前就实例化了。写法:
//最常见的写法
public class HungrySingleton {
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton() { }
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
// 静态内部类写法
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungryStaticSingleton ;
static {
hungryStaticSingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance() {
return hungryStaticSingleton;
}
}
这两种写法,并没本质上的区别,也很好理解,在类被加载的时候,我们就创建了对象。
优点:线程安全,执行效率较高
缺点:如果存在大量对象的时候,对系统的内存占用大,特别是一堆不需要使用的对象,造成严重的内存浪费。
所以我们进行优化,就开始引入懒汉式单例。
懒汉式
为了解决饿汉式单例造成的资源浪费问题,我们采用懒汉式单例,只有在需要的时候,才会被创建。
public class LazySimpleSingleton {
private static LazySimpleSingleton instance;
private LazySimpleSingleton() {}
public static LazySimpleSingleton getInstance() {
if(null == instance){
//当有多个线程执行到这里的时候,对象会被创建多个
instance = new LazySimpleSingleton();
}
return instance;
}
}
//对线程安全和效率问题做了个优化,采用双重锁校验
public class LazyDoubleSingleton {
private static volatile LazyDoubleSingleton lazyDoubleSingleton;
private LazyDoubleSingleton(){}
public static LazyDoubleSingleton getInstance() {
// 检查是否需要阻塞
if (null == lazyDoubleSingleton) {
synchronized (LazyDoubleSingleton.class) {
//检测是否需要创建对象
if (null == lazyDoubleSingleton) {
return new LazyDoubleSingleton();
}
// 指令重排序问题
}
}
return lazyDoubleSingleton;
}
}
/*
classPath:LazyStaticInnerClassSingleton.class
LazyStaticInnerClassSingleton$LazyHolder.class
优点:写法简单,很好的利用了Java本身的语法特点,性能高,避免了内存浪费
缺点:可以被反射破坏
*/
public class LazyStaticInnerClassSingleton {
private LazyStaticInnerClassSingleton(){
if (LazyHolder.instance != null) {
throw new RuntimeException("不允许非法访问");
}
}
private static LazyStaticInnerClassSingleton getInstance() {
return LazyHolder.instance;
}
private static class LazyHolder{
private static final LazyStaticInnerClassSingleton instance = new LazyStaticInnerClassSingleton();
}
}
在懒汉式中,第一种写法除了线程不安全,容易出现伪单例,测试时我们获取到了一个值,其实是创建了两次对象,而对对象进行了一个覆盖赋值。最后一种写法算是利用java本身的语法特点,进行了一个投机取巧的方式,我们加载的时候不会去加载LazyHolder内部的静态变量,在调用的时候当做对象来调用,注释上写的LazyStaticInnerClassSingleton$LazyHolder.class
注册式
注册式单例又称为登记式单例模式,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例,注册式单例分为两种,一种是枚举式,一种是容器式单例。
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
public static Object getBean(String className) {
Object instance = null;
//1、检测是否需要阻塞
if (!ioc.containsKey(className)) {
synchronized (ContainerSingleton.class) {
// 2、检测是否需要创建对象
if (!ioc.containsKey(className)) {
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
} catch (Exception e) {
e.printStackTrace();
}
return instance;
}
}
}
return ioc.get(className);
}
}
spring采用的就是容器式单例,当然spring的逻辑比上面这个例子要复杂的多,后期我们会慢慢讲。
枚举为什么是单例的,我们反编译会发现以下代码:
static{
INSTANCE = new EnumSingleton("INSTANCE",0);
$VALUES = (new EnumSingleton[]{INSTANCE});
}
所以枚举式单例也是饿汉式单例,如果我们尝试用反射去破坏会怎么样?
public static void main(String[] args){
try{
Class clazz=EnumSingleton.class;
Constructor c = clazz.getDeclaredConstructor();
c.newInstance();
}catch(Exception e){
e.printStachTrace();
}
}
结果报java.lang.NoSuchMethodException异常,意识就是没有找到无参的构造方法,我们在看Enum源码
protected Enum(String name ,int ordinal){
this.name = name;
this.ordinal = ordinal;
}
如果用暴力破解,能破坏枚举单例吗?这个大家可以去思考,并且测试一下。
枚举式单例模式是JDK最官方、稳定和权威的处理,因此也是在《Effective Java》中推荐的方式
序列化
对象写好,我们把对象序列化写入磁盘,下次再反序列转为内存对象,反序列化会重新分配内存地址,就等于重新创建了新的对象。
public class SeriableSingleton implements Serializable {
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance() {
return INSTANCE;
}
private Object readResolve() {
return INSTANCE;
}
}
我们加一个readResoulve方法就可以解决了,因为在ObjectInputStream中的readObject()方法中重写了readObject()方法,底层的invokeReadResolve()方法中,反射调用了readResolveMethod方法,找到无参的readResolve()保存下来。