本文共 3297 字,大约阅读时间需要 10 分钟。
设计模式概述见:
确保一个类有且仅有一个实例,并且提供一个全局的访问点。
通俗地说就是,对象只能生成一个,并且生成该对象的方法只有一个。单例模式的使用是为了只产生一个实例,从产生一个实例的原因及作用来看
1.对频繁生成销毁的类避免了资源的浪费。 2.达到了不同线程之间的数据共享。单例模式的经典实用,比如Windows的回收站,任务管理器,又比如避免数据库连接产生的大量开销,日志类文件、网页计数器的数据共享…等等之类
单例模式写法有多种,单论思想实现来说分为以下几种
懒汉等吃,饿汉找吃,懒汉就是在用到的时候加载实例,实现懒加载。
其中有两种懒汉的写法,区别只是是否对getInstance方法加锁来实现在多线程下的单例。
/** * @ClassName: Singleton * @Description: 懒汉单例模式 * @author cjd * @date 2017年12月18日 上午11:03:26 */ public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if(instance==null) { instance=new Singleton(); } return instance; } }
非同步懒汉删掉synchronized关键词即可,懒汉的好处在于没有使用之前节约了空间,可当使用的时候却浪费了时间。
当懒汉不加锁时,多线程下是完全不能使用的,而当懒汉加锁后,时间更进一步地被浪费,在此情况下也就出现了再后面的双重校验锁单例。饿汉与懒汉的差别是饿汉在使用前已经被虚拟机装载过并生成了唯一的实例。
/** * @ClassName: Singleton * @Description: 饿汉单例模式 * @author cjd * @date 2017年12月18日 上午11:03:26 */ public class Singleton { private static Singleton instance=new Singleton(); private Singleton() {} public static synchronized Singleton getInstance() { return instance; } }
也同时因为类加载初始化实例的方式避免了因加锁产生的开销,对此和懒汉相比付出了空间,获得了时间。
其在写法上取出了对线程的锁及实例为空判断,因为类加载的时候就已经实例化了。 以及另一种通过静态代码块只执行一次初始化的相似方法,了解一下即可。/** * @ClassName: Singleton * @Description: 饿汉单例模式 * @author cjd * @date 2017年12月18日 上午11:03:26 */ public class Singleton { private static volatile Singleton instance; static { instance = new Singleton(); } private Singleton() {} public static synchronized Singleton getInstance() { return instance; } }
此写法是为了解决在多线程环境下因懒汉模式的懒加载上锁产生大量的时间开销。
/** * @ClassName: Singleton * @Description: 双重校验锁单例模式 * @author cjd * @date 2017年12月18日 上午11:03:26 */ public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if(instance==null) { synchronized(Singleton.class) { if(instance==null) { instance=new Singleton(); } } } return instance; } }
就写法和第一种懒汉单例进行比较,在getInstance()方法内部对对象进行了两次非空判断,节约了大量上锁的时间开销。
同时对instance对象引入了一个volatile的关键词 在不加此关键词的情况下,尽管概率会比较低,还是有可能会发生出现两个实例对象的情况。 因为jvm在instance=new Singleton()的时候经历了3个步骤 1.为对象分配内存 2.初始化实例对象 3.把引用instance指向分配的内存空间 如果在jvm优化后的顺序为1,3,2 ,当线程1进入new Singleton(),先执行了3,instance已经不为null了。 此时线程2进入判断instance不为null,返回instance导致程序报错。 而volatile关键词限制了其不能进行重排,保证了3步骤在最后一步完成,则不会出现上述的问题。静态内部类的实现即将内部类及内部类私有
这样写的好处在于同样实现了懒加载public class Singleton { private Singleton() {} private static class SingletonHolder { private static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } }
枚举的方法写在最后是因为其实在过于简洁方便。双重校验锁因Java5之后产生了 volatile关键词所以没有了之前低概率产生的问题。
枚举法则一直没有这样的问题,而且更加简洁,为什么简洁,以下是代码/** * @ClassName: Singleton * @Description: 枚举单例模式 * @author cjd * @date 2017年12月18日 上午11:03:26 */ public enum Singleton{ INSTANCE; }
调用方法也很简单,Singleton.INSTANCE;
单例模式的滥用同样会产生很大的问题,设计时也不能一昧地为了节约资源开销而使用单例。
同时也因为反射的机制存在,可能会产生多个单例对象的情况,应尽量避免使用反射创建单例。转载地址:http://ariob.baihongyu.com/