四:Java设计模式之单例模式
一、单例模式
单例模式一种常用的软件设计模式,在它的核心结构中值包含一个被称为单例的特殊类。一个类只有一个实例,即一个类只有一个对象实例。在spring框架中也是保证了所管理的对象都是单例模式;
二、常用的单例模式
1、饿汉单例
// 它是在类加载的时候就立即初始化,并且创建单例对象 //优点:没有加任何的锁、执行效率比较高, //在用户体验上来说,比懒汉式更好 //缺点:类加载的时候就初始化,不管你用还是不用,我都占着空间 //浪费了内存,有可能占着茅坑不拉屎 //绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题 public class HungrySingleton { //私有的构造函数 private HungrySingleton() { } private static final HungrySingleton hungrySingleton = new HungrySingleton(); public static HungrySingleton getInstance() { return hungrySingleton; } }
//静态块写法实现饿汉单例 public class HungryStaticSingleton { private HungryStaticSingleton() { } private static final HungryStaticSingleton hungrySingleton; //静态块 static { hungrySingleton = new HungryStaticSingleton(); } public HungryStaticSingleton getInstance() { return hungrySingleton; } }
2、懒汉式单例
public class LazySingleton { //懒汉式单例 //解决了饿汉式单例加载资源浪费问题 //但是会出现新的问题:线程安全问题 private LazySingleton() { } private static LazySingleton lazySingleton = null; //在外部需要的时候才会去初始化。 public LazySingleton getInstance() { if (lazySingleton == null) { return new LazySingleton(); } return lazySingleton; } }
3、线程安全的懒汉单例
public class LazySynchrinizedSingleton { private LazySynchrinizedSingleton() { } private static LazySynchrinizedSingleton lazySingleton = null; //synchronized的关键字保证线程安全,但是synchronized 性能较低,虽然在JDK1.6做了相关性能的优化 public synchronized static LazySynchrinizedSingleton getInstance() { if (lazySingleton == null) { return new LazySynchrinizedSingleton(); } return lazySingleton; } }
4、双重锁校验的懒汉单例
public class LazyDoubleCheckSingleton { private LazyDoubleCheckSingleton() { } private static LazyDoubleCheckSingleton lazySingleton = null; //双重锁校验,虽然比前面一种直接在getInstance加synchronize关键字效率高,但是 //任然使用了synchronize关键字 public static LazyDoubleCheckSingleton getInstance() { if (lazySingleton == null) { synchronized (LazyDoubleCheckSingleton.class) { if (lazySingleton == null) { return new LazyDoubleCheckSingleton(); } } } return lazySingleton; } }
双重同步锁单例模式:这个方法虽然使用了双重校验并且加锁但是任然是线程不安全的单例模式;
jvm底层对象实例化的过程是:
1、memory = allocate() 分配对象的内存空间
2、cotrInstance() 初始化对象
3、instance = momory 设置instance指向刚分配的内存
但是根据指令重排定义,可能存在以下执行顺序
1、memory = allocate() 分配对象的内存空间
3、instance = momory 设置instance指向刚分配的内存
2、cotrInstance() 初始化对象
所以在多线程执行的时候可能存在:A线程执行,进入一层为空判断发现对象为空,加锁后进入对象实例化,执行到1,3步骤后,线程B线程调用此方法,此时由于线程A实例对象已经指向了分配的内存地址,所以一层判断是instance不等于null,直接返回instance,但是此时
返回的instance是尚未初始化的实例对象;
5、双重同步锁线程安全单例模式
引入volatile关键字解决模式4的jvm指令重排序问题
public class LazyVolatileDoubleCheckSingleton { private LazyVolatileDoubleCheckSingleton() { } //volatile解决JVM指令重排序问题 private volatile static LazyVolatileDoubleCheckSingleton lazySingleton = null; public static LazyVolatileDoubleCheckSingleton getInstance() { if (lazySingleton == null) { synchronized (LazyVolatileDoubleCheckSingleton.class) { if (lazySingleton == null) { return new LazyVolatileDoubleCheckSingleton(); } } } return lazySingleton; } }
6、通过静态内部类实现单例
public class LazyInnerSingleton { private LazyInnerSingleton() { } //每一个关键字都不是多余的 //static 是为了使单例的空间共享 //保证这个方法不会被重写,重载 public static LazyInnerSingleton getInstance() { return LazyMake.lazy; } //巧妙的利用的静态内部类的加载机制,可以完美的实现线程安全的 //默认不加载。只有调用的时候才会去加载,实现懒加载 private static class LazyMake { private static final LazyInnerSingleton lazy = new LazyInnerSingleton(); } }
以上6种单例都存在相同的问题,就是可能会被反射或者序列化给破坏。
问题:既然是单例模式,有可能还去实现Serializable接口去让别人去序列化和反序列化吗?
常用的深克隆方式用到的就是序列化生成不同的对象,如果是单例模式,是不需要产生多个对象的,
由此应该是不需要实现序列化接口的。
当然,就算是实现了Serializable接口,能够序列化,也是有方法可以保持单例的。
总而言之,看自己项目中实际运用场景,只要你理解了各种单例的场景、优缺点,就能够灵活运用啦。
7、枚举实现单例(枚举也算一种注册式单例)(《Effective Java》推荐使用的单例方式)
/常量中去使用,常量不就是用来大家都能够共用吗? //通常在通用API中使用 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; } }
8、注册式单例
public class ContainerSingleton { private ContainerSingleton() { } private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>(); public static Object getInstance(String className) { synchronized (ioc) { if (!ioc.containsKey(className)) { Object obj = null; try { obj = Class.forName(className).newInstance(); ioc.put(className, obj); } catch (Exception e) { e.printStackTrace(); } return obj; } else { return ioc.get(className); } } } }