首先,设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。设计模式总共有23种之多,这里就是单例模式中的内容。
1、单例模式的定义及特点
单例(Singleten)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。
单例模式在现实生活中的应用也非常广泛,例如公司 CEO、部门经理等都属于单例模型。J2EE 标准中的 ServletContext 和 ServletContextConfig、Spring 框架应用中的 ApplicationContext、数据库中的连接池等也都是单例模式。
单例模式有 3 个特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点。
2、单例模式的优缺点
单例模式的优点:
- 单例模式可以保证内存里只有一个实例,减少了内存的开销。
- 可以避免对资源的多重占用。
- 单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点:
- 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
- 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
3、单例模式的结构以及七种实现方式
单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。例模式的主要角色如下。
3.1单例模式的结构
- 单例类:包含一个实例且能自行创建这个实例的类。
- 访问类:使用单例的类。
其结构如图 1 所示。
图1 单例模式的结构图
3.2单例模式的实现
3.2.1懒汉式单例
其特点是在类加载时不生成单例,只有在第一次调用单例类对外提供一个访问该单例的全局访问点时,才生成单例
public class LazySingleten {
private static volatile LazySingleten instance = null; //保证 instance 在所有线程中同步
private LazySingleten() {
} //private 避免类在外部被实例化
public static synchronized LazySingleten getInstance() {
//getInstance 方法前加同步
if (instance == null) {
instance = new LazySingleten();
}
return instance;
}
}
其中的volatile和synchronized关键字是保证线程安全的,如果不加这两个关键字那么该单例模式是不安全的。如果加上这两个关键字就能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。
synchronized关键字是一种同步锁,被它所修饰之后同一时刻只允许一个线程去访问它,它有以下几种修饰对象
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
关于加不加volatile关键字:
不加volatile的情况下,假设两个线程,线程A正在执行instance = new Instance()的操作,而线程B开始执行if(instance==null)的判断,当不存在volatile的时候,因为 new Instance()是一个非原子操作,可能发生无序写入,构造函数可能在整个对象构造完成前执行完毕,线程B可能会看到一个不完整的instance对象,因为java的某些实现会在内存中开辟一片存储对象的区域后直接返回内存的引用,所以线程B判断不为null,而这时候实际上,instance的构造函数还没有执行,从而线程b得到不完整的对象。在 Instance 的构造函数执行之前,会在内存中开辟一片存储对象的区域后直接返回内存的引用,赋值给变量 instance,instance也就可能成为非 null 的,即赋值语句在对象实例化之前调用,此时别的线程得到的是一个还会初始化的对象,这样会导致系统崩溃线程B可能会看到一个不完整的instance对象,因为java的某些实现,所以线程B判断不为null。从而得到不完整的对象
3.2.2饿汉式单例
该模式的特点是类一旦加载就创建一个单例,保证在调用单例类对外提供一个访问该单例的全局访问点之前单例已经存在了。且不存在懒汉式单例的线程安全问题
public class HungrySingleten {
private static final HungrySingleten instance = new HungrySingleten();
private HungrySingleten() {
}
public static HungrySingleten getInstance() {
return instance;
}
}
3.2.3双重检查模式(DCL)
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){
}
public static Singleton getInstance() {
if (instance== null) {
synchronized (Singleton.class) {
if (instance== null) {
instance= new Singleton();
}
}
}
return singleton;
}
}
这种写法在getSingleton方法中对singleton进行了两次判空,第一次是为了不必要的同步,第二次是在singleton等于null的情况下才创建实例。
在这里使用volatile会或多或少的影响性能,但考虑到程序的正确性,牺牲这点性能还是值得的。 DCL优点是资源利用率高,第一次执行getInstance时单例对象才被实例化,效率高。缺点是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷,虽然发生的概率很小。DCL虽然在一定程度解决了资源的消耗和多余的同步,线程安全等问题,但是他还是在某些情况会出现失效的问题,也就是DCL失效,
3.2.4静态内部类单例模式
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
第一次加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance方法时虚拟机加载SingletonHolder 并初始化sInstance ,这样不仅能确保线程安全也能保证Singleton类的唯一性
3.2.5枚举单例
public enum Singleton {
INSTANCE;
public void doSomeThing() {
}
}
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例,上述讲的几种单例模式实现中,有一种情况下他们会重新创建对象,那就是反序列化,将一个单例实例对象写到磁盘再读回来,从而获得了一个实例。反序列化操作提供了readResolve方法,这个方法可以让开发人员控制对象的反序列化。在上述的几个方法示例中如果要杜绝单例对象被反序列化是重新生成对象,就必须加入如下方法:
private Object readResolve() throws ObjectStreamException{
return singleton;
}
枚举单例的优点就是简单,但是大部分应用开发很少用枚举,可读性并不是很高,不建议用。
3.2.6使用容器实现单例模式
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String,Object>();
private Singleton() {
}
public static void registerService(String key, Objectinstance) {
if (!objMap.containsKey(key) ) {
objMap.put(key, instance) ;
}
}
public static ObjectgetService(String key) {
return objMap.get(key) ;
}
}
用SingletonManager 将多种的单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度
3.2.7登记式单例
//类似Spring里面的方法,将类名注册,下次从里面直接获取。
public class Singleton3 {
private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();
static{
Singleton3 single = new Singleton3();
map.put(single.getClass().getName(), single);
}
//保护的默认构造子
protected Singleton3(){}
//静态工厂方法,返还此类惟一的实例
public static Singleton3 getInstance(String name) {
if(name == null) {
name = Singleton3.class.getName();
System.out.println("name == null"+"--->name="+name);
}
if(map.get(name) == null) {
try {
map.put(name, (Singleton3) Class.forName(name).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return map.get(name);
}
//一个示意性的商业方法
public String about() {
return "Hello, I am RegSingleton.";
}
public static void main(String[] args) {
Singleton3 single3 = Singleton3.getInstance(null);
System.out.println(single3.about());
}
}
登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。
这里我对登记式单例标记了可忽略,我的理解来说,首先它用的比较少,另外其实内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了。
原文链接:https://blog.csdn.net/jason0539/article/details/23297037/
3.3破坏单例模式的三种方法
反射,序列化,克隆
/**
* 序列化对单例的破坏
* @author Administrator
*
*/
public class SingletonTest09 {
public static void main(String[] args) throws Exception{
System.out.println("-----------序列化----------------------");
Singleton originSingleton = Singleton.getInstance();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(Singleton.getInstance());
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Singleton serializeSingleton = (Singleton) ois.readObject();
System.out.println(originSingleton == serializeSingleton);//false
System.out.println("-----------反射----------------------");
//通过反射获取
Constructor<Singleton> cons = Singleton.class.getDeclaredConstructor();
cons.setAccessible(true);
Singleton reflextSingleton = cons.newInstance();
System.out.println(reflextSingleton == originSingleton);//false
System.out.println("---------------------------克隆----------------------");
Singleton cloneSingleton = (Singleton) originSingleton.clone();
System.out.println(cloneSingleton == originSingleton);//false
}
private static class Singleton implements Serializable,Cloneable{
/**
* 1.构造方法私有化,外部不能new
*/
private Singleton() {}
//2.本类内部创建对象实例
private static volatile Singleton instance;
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
}
解决方案
1、防止反射
定义一个全局变量,当第二次创建的时候抛出异常
2、防止克隆破坏
重写clone(),直接返回单例对象
3、防止序列化破坏
添加readResolve(),返回Object对象
**
* 序列化对单例的破坏,解决方案
* @author Administrator
*
*/
public class SingletonTest10 {
public static void main(String[] args) throws Exception{
System.out.println("-----------序列化----------------------");
Singleton originSingleton = Singleton.getInstance();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(Singleton.getInstance());
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Singleton serializeSingleton = (Singleton) ois.readObject();
System.out.println(originSingleton == serializeSingleton);//false
System.out.println("-----------反射----------------------");
//通过反射获取
Constructor<Singleton> cons = Singleton.class.getDeclaredConstructor();
cons.setAccessible(true);
Singleton reflextSingleton = cons.newInstance();
System.out.println(reflextSingleton == originSingleton);//false
System.out.println("---------------------------克隆----------------------");
Singleton cloneSingleton = (Singleton) originSingleton.clone();
System.out.println(cloneSingleton == originSingleton);//false
}
private static class Singleton implements Serializable,Cloneable{
private static volatile boolean isCreate = false;//默认是第一次创建
/**
* 1.构造方法私有化,外部不能new
*/
private Singleton() {
if(isCreate) {
throw new RuntimeException("已然被实例化一次,不能在实例化");
}
isCreate = true;
}
//2.本类内部创建对象实例
private static volatile Singleton instance;
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return instance;
}
/**
* 防止序列化破环
* @return
*/
private Object readResolve() {
return instance;
}
}
}
关于破坏单例模式的问题 详情也可参考 https://blog.csdn.net/fd2025/article/details/79711198