这个模式是很有意思,而且比较简单,但是我还是要说因为它使用的是如此的广泛,如此的有人缘,单例就是单一、独苗的意思,那什么是独一份呢?你的思维是独一份,除此之外还有什么不能山寨的呢?我们举个比较难复制的对象:皇帝,中国的历史上很少出现两个皇帝并存的时期,是有,但不多,那我们就认为皇帝是个单例模式,在这个场景中,有皇帝,有大臣,大臣是天天要上朝参见皇帝的,今天参拜的皇帝应该和昨天、前天的一样(过渡期的不考虑,别找茬哦),大臣磕完头,抬头一看,嗨,还是昨天那个皇帝,单例模式,绝对的单例模式,先看类图:
然后我们看程序实现,先定一个皇帝:
/**
* @Description:
* 中国的历史上一般都是一个朝代一个皇帝,有两个皇帝的话,必然要PK出一个皇帝出来
*/
public class Emperor {
// 定义一个皇帝放在那里,然后给这个皇帝名字
private static Emperor emperor = null;
private Emperor() {
// 世俗和道德约束你,目的就是不让你产生第二个皇帝
}
public static Emperor getInstance() {
// /如果皇帝还没有定义,那就定一个
if(emperor == null) {
emperor = new Emperor();
}
return emperor;
}
// 皇帝叫什么名字呀
public static void emperorInfo() {
System.out.println("吾乃康熙大帝...");
}
}
然后定义大臣:
/**
* @Description:
* 大臣是天天要面见皇帝,今天见的皇帝和昨天的,前天不一样那就出问题了!
*/
public class Minister {
public static void main(String[] args) {
// 第一天
Emperor emperor1 = Emperor.getInstance();
emperor1.emperorInfo();
// 第二天
Emperor emperor2 = Emperor.getInstance();
emperor2.emperorInfo();
// 第二天
Emperor emperor3 = Emperor.getInstance();
emperor3.emperorInfo();
// 三天见的皇帝都是同一个人,荣幸吧!
}
}
结果:
吾乃康熙大帝...
吾乃康熙大帝...
吾乃康熙大帝...
看到没,大臣天天见到的都是同一个皇帝,不会产生错乱情况,反正都是一个皇帝,是好是坏就这一个,只要提到皇帝,大家都知道指的是谁,清晰,而又明确。问题是这是通常情况,还有个例的,如同一个时期同一个朝代有两个皇帝,怎么办? 看看这篇文章吧:Java设计模式之多例模式
单例模式很简单,就是在构造函数中多了加一个构造函数,访问权限是private的就可以了,这个模式是简单,但是简单中透着风险,风险?什么风险?在一个B/S项目中,每个HTTPRequest请求到J2EE的容器上后都创建了一个线程,每个线程都要创建同一个单例对象,怎么办?我们写一个通用的单例程序,然后分析一下:
我们来看黄色的那一部分,假如现在有两个线程A和线程B,线程A执行到singletonPattern = new SingletonPattern()
,正在申请内存分配,可能需要0.001微秒,就在这0.001微秒之内,线程B执行到if(singletonPattern == null)
,你说这个时候这个判断条件是true还是false?是true,那然后呢?线程B也往下走,于是乎就在内存中就有两个SingletonPattern的实例了,这就出问题了。如果你这个单例是去拿一个序列号或者创建一个信号资源的时候,会导致业务逻辑混乱!数据一致性校验失败!最重要的是你从代码上还看不出什么问题,这才是最要命的!因为这种情况基本上你是重现不了的,不寒而栗吧,那怎么修改?有很多种方案,我就说一种,能简单的、彻底解决这个问题的方案:
public class SingletonPattern {
private static final SingletonPattern singletonPattern = new SingletonPattern();
private SingletonPattern() {
}
public synchronized static SingletonPattern getInstance() {
return singletonPattern;
}
}
直接new一个对象传递给类的成员变量singletonPattern,你要的时候getInstance()直接返回给你,解决问题!
以上内容原书为:
《您的设计模式》 作者:CBF4LIFE
作者提到,有多种实现单例模式的方案,接下来,我来介绍着几种方案,并进行比较
package com.cbf4life.singleton;
/**
* @Description:
* 饿汉式单例模式
* 饿汉式单例模式中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题
* 虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题,因此可以省略synchronized关键字
* 问题:如果只是加载了本类,而没有调用getInstance()方法,那么这个单例对象存在就没有意义,造成资源浪费
*/
public class SingletonPattern {
private SingletonPattern() {
}
private static SingletonPattern instance = new SingletonPattern();
public static SingletonPattern getInstance() {
return instance;
}
}
/**
* @Description:
* 懒汉式单例模式
* 优点:lazy load!延迟加载,懒加载!真正用的时候才加载
* 问题:资源利用率高了,但是,每次调用getInstance()方法都要同步,并发效率较低!
*/
public class SingletonPattern {
private SingletonPattern() {
}
private static SingletonPattern instance;
public static synchronized SingletonPattern getInstance() {
if(instance == null) {
instance = new SingletonPattern();
}
return instance;
}
}
package com.cbf4life.singleton;
/**
* @Description:
* 双重检测锁式单例模式
* 优点:这个模式将同步内容写到if()内部,提高了执行的效率
* 不必每次获取对象都进行同步,只有第一次才同步,创建以后就没有必要了
* 问题:由于编译器优化和JVM底层内部模型原因,偶尔会出现问题。不建议使用
*/
public class SingletonPattern {
private SingletonPattern() {
}
private static SingletonPattern instance = null;
public static SingletonPattern getInstance() {
if(instance == null) {
SingletonPattern sp;
synchronized (SingletonPattern.class) {
sp = instance;
if(sp == null) {
synchronized (SingletonPattern.class) {
if(sp == null) {
sp = new SingletonPattern();
}
}
instance = sp;
}
}
}
return instance;
}
}
package com.cbf4life.singleton;
/**
* @Description:
* 静态内部类式单例模式
* 优点:外部类没有static属性,不会像饿汉式那样立即加载
* 只有真正调用getInstance()方法,才会加载静态内部类,加载类时是线程安全的
* instance是static final类型,保证了内存中只有这样一个实例存在而且只能被赋值一次
* 从而保证了线程安全性,兼顾了并发高效调用和延迟加载的优势
*/
public class SingletonPattern {
private SingletonPattern() {
}
private static SingletonPattern instance = null;
private static class SingletonPatternInstance {
private static final SingletonPattern instance = new SingletonPattern();
}
public static SingletonPattern getInstance() {
return SingletonPatternInstance.instance;
}
}
/**
* @Description:
* 枚举单例
* 优点:实现简单,枚举本身就是单例模式
* 由JVM从根本上提供保障,避免发射和序列化得漏洞!
* 问题:不能延迟加载
*/
public enum SingletonPattern {
// 定义一个枚举的元素,它就代表了SingletonPattern的一个实例
INSTANCE;
public static void main(String[] args) {
// 需要的时候直接调用就可以
SingletonPattern sp = SingletonPattern7.INSTANCE;
// 其他操作...
}
}
单例对象占用资源少,不需要延时加载:枚举式好于饿汉式
单例对象占用资源多,需要延时加载:静态内部类式好于懒汉式
我们说到,除了枚举单例模式外,其他的模式都可以被发射和反序列化破解,那么是怎么破解的?看下面的代码
package com.jed.singleton;
import java.lang.reflect.Constructor;
/**
* @Description:
* 测试反射破解单例模式
*/
public class Demo {
private Demo() {
}
private static Demo instance = new Demo();
public static Demo getInstance() {
return instance;
}
public static void main(String[] args) throws Exception {
Demo d1 = Demo.getInstance();
Demo d2 = Demo.getInstance();
System.out.println(d1 == d2);// true
// 通过反射的方法破解单例模式
Class<Demo> cla = (Class<Demo>) Class.forName("com.jed.singleton.Demo");
// 获得无参构造器
Constructor<Demo> constructor = cla.getDeclaredConstructor(null);
// 设置跳过权限检查,可以访问私有成员
constructor.setAccessible(true);
Demo d3 = constructor.newInstance();
Demo d4 = constructor.newInstance();
System.out.println(d3 == d4);// false
}
}
解决办法,改写私有的构造方法
private Demo() {
if(instance != null) {
throw new RuntimeException("instance存在,不允许再调用构造器!");
}
}
package com.jed.singleton;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* @Description:
* 测试反序列化破解单例模式
* @author Jed
* @date 2018年1月8日
*/
public class Demo implements Serializable {
private static Demo instance ;
private Demo() {
}
public static synchronized Demo getInstance() {
if(instance == null) {
instance = new Demo();
}
return instance;
}
public static void main(String[] args) throws Exception {
Demo d1 = Demo.getInstance();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:/object.txt"));
out.writeObject(d1);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("E:/object.txt"));
Demo d2 = (Demo) in.readObject();
System.out.println(d1 == d2);// false
in.close();
}
}
解决办法,在类中添加readResolve()这个方法
反序列化直接调用readResolve()返回指定的对象,不需要再创建新对象
public Object readResolve() {
return instance;
}