单例模式:指一个类有且仅有一个实例

由于单例模式只允许有一个实例,所以单例类就不可通过new来创建,而所有对象都默认有一个无参的构造函数可以创建对象,所以单例类不仅不能提供public的构造方法,
还需要重写默认的无参构造方法。由于单例类不可再new创建,所以需要有一个公用的实例需要创建好并返回,所以单例类还需要有一个返回单例对象的方法。且这个方法还必须是静态的方法,否则此方法无法在其他地方调用。综上所述,单例类的大致结构如下:

 public class SingletonDemo {

 private static SingletonDemo singleton = new SingletonDemo();//单例的对象实例

 //重写无参构造方法,改为私有private修饰
private SingletonDemo(){
} //返回单例对象
public static SingletonDemo getInstance() {
return singleton;
}
}

或者改为

 public class SingletonDemo {

 private static SingletonDemo singleton;//单例的对象实例

 static{
singleton = new singletonDemo();
} //重写无参构造方法,改为私有private修饰
private SingletonDemo(){
} //返回单例对象
public static SingletonDemo getInstance() {
return singleton;
}
}

两个方式效果一样,都是在SingletonDemo类加载的时候进行实例化,这种单例方式叫做饿汉式,即类加载的时候就进行了初始化。此时如果这个类的初始化过程比较消耗资源,而这个单例类又一直用不到的话,那么就会浪费过多的资源,如果不想这样的话,还有一种方式是懒汉式,即类加载的时候不进行初始化,而是在使用的时候才初始化。
大致结构如下:

 public class SingletonDemo {

 private static SingletonDemo singleton;// 单例的对象实例

 // 重写无参构造方法,改为私有private修饰
private SingletonDemo() {
} // 返回单例对象
public static SingletonDemo getInstance() {
//判断singleton是否初始化,没有则初始化
if (singleton == null) {
singleton = new SingletonDemo();
}
return singleton;
}
}

这种方式是类加载的时候不进行初始化,而是在使用的时候先判断单例对象是否初始化,没有的话才进行初始化。这种方式虽然解决了饿汉式的消耗资源问题,但是这种方式很显然会有多线程不安全问题,如果两个线程同时执行getInstance方法,而此时singleton都为null,则两个线程都会执行singleton=new singleton(),从而创建了两个实例,很显然违背了单例模式只有一个实例的原则,当然这种方式在单线程的情况下是没有任何问题的。
但是在多线程情况下就需要让这个getInstance方法变的线程安全,可以加上synchronized关键字进行修饰,如下:

 public class SingletonDemo {

 private static SingletonDemo singleton;// 单例的对象实例

 // 重写无参构造方法,改为私有private修饰
private SingletonDemo() {
} // 返回单例对象(加锁处理防止多线程并发问题)
public static synchronized SingletonDemo getInstance() {
if (singleton == null) {
singleton = new SingletonDemo();
}
return singleton;
}
}

此中方式看似没有什么问题,但是单例模式的初始化毕竟只需要初始化一次,为了唯一一次的初始化的时候线程安全而加锁处理,会导致之后每次获取单例实例的时候都会遇到加锁处理,这显然是很影响效率的。所以需要有一种既能延迟加载又是线程安全的方法又不能加锁的方式,使用静态内部类方式便可以解决这一问题,如下:

 public class SingletonDemo {

 //静态内部类,包含单例的实例
public static class SingletonDemoHolder{
private static final SingletonDemo singleton = new SingletonDemo();
} // 重写无参构造方法,改为私有private修饰
private SingletonDemo() {
} // 返回单例对象
public static SingletonDemo getInstance() {
return SingletonDemoHolder.singleton;
} }

这种方式在类加载的时候只会加载SingletonDemo类,而没有加载SingletonDemoHolder类,只有调用了getInstance方法的时候才会加载SingletonDemoHolder,并且才会初始化singleton这个单例对象,这样就达到了延迟加载的效果,而singletonDemoHolder类的加载过程只可能会有一个线程会执行,所以同时也保证了singleton实例不会有多线程安全问题,这也是目前比较普遍的用法。

But,虽然这种写法能支持懒加载,又解决线程安全性问题,但是还无法保证实例的单一问题,因为Java中创建一个对象不仅仅可以通过new来创建,还可以根据反射和反序列化来创建。这就导致来通过反射和反序列化创建的对象和单例中的对象不是同一个,从而就破坏来单例模式只有一个实例的规则。案例如下:

 public class SingletonMain {

 public static void main(String[] args)throws Exception{
Class cla = SingletonDemo.class;//获取Class对象
Constructor constructor = cla.getDeclaredConstructor();//获取构造方法
constructor.setAccessible(true);//设置跳过检查,也就是不检查构造器是否是private修饰
SingletonDemo instance1 = (SingletonDemo)constructor.newInstance();//通过构造器创建对象1
SingletonDemo instance2 = SingletonDemo.getInstance();//通过单例获取对象2 System.out.println(instance1.toString());
System.out.println(instance2.toString());
}
}

结果如下:

1 com.lucky.design.singleton.SingletonDemo@511d50c0
2 com.lucky.design.singleton.SingletonDemo@60e53b93

很明显创建了两个不同的SingletonDemo对象,破坏了单例模式

同样的先通过将单例的实例进行序列化然后再进行反序列化获取到的对象同样也和单例的对象不一样,有兴趣的同学可以自行测试下。而解决方案是在单例类中重写readResolve方法。如下:
private Object readResolve(){
return instance;//直接返回单例中的对象
}
但是这个解决方法虽然能够防止JDK自动的序列化和反序列化机制,但是无法防止其他的序列化方式,比如alibaba的fastjson的序列化,如下:

 public static void main(String[] args)throws Exception{
SingletonDemo instance1 = SingletonDemo.getInstance();
String str = JSON.toJSONString(instance1);
SingletonDemo instance2 = JSON.parseObject(str,SingletonDemo.class); System.out.println(instance1.toString());
System.out.println(instance2.toString());
}

结果为:

1 com.lucky.design.singleton.SingletonDemo@573fd745
2 com.lucky.design.singleton.SingletonDemo@78e03bb5

虽然已经加了readResolve方法,但是还是无法防止所有序列化和反序列化,因为每种序列化和反序列化的算法都是不一样的。

所以在不考虑反射和序列化的情况下,采用内部类的单例方式就足够了,但是如果考虑这两种情况,显然内部类的方式也不保险。目前而言最保险且最简洁的方式是枚举类的方式,代码如下:

 package com.lucky.design.singleton;
/**
* 枚举类单例
* */
public enum SingletonDemo {
intance; //其他静态方法
}

只需要在枚举类中定义一个选项即可,这个唯一选项也就是这个枚举类的唯一实例。测试代码如下:

 public static void main(String[] args)throws Exception{
SingletonDemo instance1 = SingletonDemo.intance;
String str = JSON.toJSONString(instance1);
SingletonDemo instance2 = JSON.parseObject(str,SingletonDemo.class); System.out.println(instance1.toString());
System.out.println(instance2.toString());
System.out.println(instance1==instance2);
}

结果为:

 intance
intance
true

很显然达到了单例唯一的效果,而且枚举类的方式的写法还是最简洁的,目前也是最受欢迎的一种写法了。
枚举类被编译之后默认是继承之抽象了Enum类,且是final类型的,那么枚举类能否避免被反射或是反序列化呢?答案是yes
首先看下如何避免反射:
反射的机制是通过获取类的Class对象,如何获取构造器Constructor对象,如何调用newInstance方法来进行创建,那么看下newInstance方法的源码:

 @CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}

可以看到当类是Enum类型是,会直接抛出不能反射创建enum类型的对象异常,所以通过反射是无法创建枚举类型的实例

再看下如何避免被序列化:
对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject会破坏单例的问题。

Java常用设计模式详解1--单例模式的更多相关文章

  1. PHP常用设计模式,PHP常用设计模式详解,PHP详解设计模式,PHP设计模式

    PHP常用设计模式详解 单例模式: php交流群:159789818 特性:单例类只能有一个实例 类内__construct构造函数私有化,防止new实例 类内__clone私有化,防止复制对象 设置 ...

  2. Java之设计模式详解 (转)

    转载:http://blog.csdn.net/zhangerqing/article/details/8194653 设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模 ...

  3. java常用集合详解 contains

    java集合是对常用数据集合的封装,差不多就是数组吧,验证某个元素是否在数据集合里,最原始的方法是,用个循环,"某个元素"与数据集合中的每个元素逐个进行比较. java 对常用的一 ...

  4. Java常用集合类详解

    在Java中有一套设计优良的接口和类组成了Java集合框架,使程序员操作成批的数据或对象元素极为方便.所有的Java集合都在java.util包中. 在编写程序的过程中,使用到集合类,要根据不同的需求 ...

  5. php 常用设计模式详解

    1.单例模式 构造函数必须为private 一个保存类实例静态成员变量 拥有一个访问这个实例的公共静态方法(常用getInstance()方法进行实例化单例类,通过instanceof操作符可以检测到 ...

  6. java常用集合类详解(有例子,集合类糊涂的来看!)

    Framework集合框架是一个统一的架构,用来表示和操作集合.集合框架主要是由接口,抽象类和实现类构成.接口:蓝色:实现类:红色Collection|_____Set(HashSet)|       ...

  7. Java常用类详解

    目录 1. String类 1.1 String的特性 1.2 String字面量赋值的内存理解 1.3 String new方式赋值的内存理解 1.4 String 拼接字面量和变量的方式赋值 1. ...

  8. [ 转载 ] Java开发中的23种设计模式详解(转)

    Java开发中的23种设计模式详解(转)   设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类 ...

  9. Java温故而知新(5)设计模式详解(23种)

    一.设计模式的理解 刚开始“不懂”为什么要把很简单的东西搞得那么复杂.后来随着软件开发经验的增加才开始明白我所看到的“复杂”恰恰就是设计模式的精髓所在,我所理解的“简单”就是一把钥匙开一把锁的模式,目 ...

随机推荐

  1. Visual Studio 2015 + Windows 2012 R2, c++/cli Array::Sort() 抛出异常

    在Windows7上编译就是正常. 可见Windows2012 R2缺少了一些东西. 另外,有一个现象一样,但原因不一样的 https://stackoverflow.com/questions/46 ...

  2. Android:RelativeLayout 内容居中

    Android RelativeLayout 内容居中解决办法: 使用Linearlayout本来利用父控件的gravity属性是很好解决的.但是对应RelativeLayout虽然有gravity属 ...

  3. java 设计模式-责任链

    责任链设计模式,其实就是处理同一个请求的对象连接成一条链,请求的路径经过这条链,符合要求的就处理这个请求,不符合就接着往下面抛出,直道有人处理这条请求. 业务:比如啊,公司个人请假,三天以下就是主管审 ...

  4. 团队中的 Node.js 具体实践

    前天,我们公司前端团队的几个人一起去大搜车参加了芋头所组织的「搜车 Node Party」.这是我第一次参加与 Node.js 相关的线下聚会,如果不算「杭JS」的话. 聚会现场 这次聚会的主题全部是 ...

  5. 「每天一道面试题」Java类的生命周期包括哪几个阶段?

    一个Java类被加载到虚拟机中,它的生命周期才算开始,直到被从内存中卸载,它的生命周期才算结束.从开始到结束,它的整个生命周期包括加载.验证.准备.解析.初始化.使用和卸载7个阶段,其中验证.准备和解 ...

  6. 数学--数论--直角三角形--勾股数---奇偶数列法则 a^2+b^2=c^2

    先说勾股数: 勾股数,又名毕氏三元数 .勾股数就是可以构成一个直角三角形三边的一组正整数.勾股定理:直角三角形两条直角边a.b的平方和等于斜边c的平方(a²+b²=c²) 勾股数规律: 首先是奇数组口 ...

  7. Codeforce 270D Greenhouse Effect

    Emuskald is an avid horticulturist and owns the world's longest greenhouse - it is effectively infin ...

  8. docker cannot open directory .: Permission denied无权限问题

    docker运行一个容器后,将主机中当前目录下的文件夹挂载到容器的文件夹后 进入到docker容器内对应的挂载目录中,运行命令ls后提示: ls: cannot open directory .: P ...

  9. libevent(十)bufferevent 2

    接上文libevent(九)bufferevent 上文主要讲了bufferevent如何监听读事件,那么bufferevent如何监听写事件呢? 对于一个fd,只要它的写缓冲区没有满,就会触发写事件 ...

  10. Java——枚举

    枚举类简介: Java5新增了一个enum关键字(它与class.interface关键字的地位相同),用以定义枚举类.枚举类也是一种特殊的类,所以也具有和类相同的变量和方法,也可以定义自己的构造器. ...