本设计模式资料源于慕课网,讲师:Geely 《Java设计模式精讲 Debug方式+内存分析》,本人作为自己手打整理作为学习的归纳总结。
一,模式介绍
1,定义和类型
保证一个类仅有一个类,并提供一个全局访问点
类型:创建型
2,适用场景
想确保任何情况下都只有一个实例
3,优点
- 在内存里只有一个实例,减少了内存开销
- 可以避免对资源的多重占用
- 设置全局访问点,严格控制访问点
4,缺点
- 没有接口,扩展困难
5,重点
- 私有构造器
- 线程安全
- 延迟加载
- 序列化和反序列化
- 反射
6,相关设计模式
- 单例模式和工厂模式
- 单例模式和享元模式
二,代码演示
1,懒汉式
- 懒汉式可以按照字面意思这么理解,现在要完成一个事情(指返回单例),但是这个办事的人就比较懒(指生成单例的这个类),于是这个人就只把要生成的类先给你设置成 null ;等到你要用的时候他才给你 new 出来。要不然就一直放在那。
- 这里值得注意的是同步锁的问题,同一时间只能有一个线程去调用到这个类的生成方法,确保获取的是同一个对象,或者也可以说是不用重复生成同一个类。
package com.try1.design.creational.singleton;
/**
* 懒汉式
*/
public class LazySingleton {
private static LazySingleton lazySingleton=null;
private LazySingleton(){
}
public synchronized static LazySingleton getInstance(){
if (lazySingleton==null){ //检查
lazySingleton=new LazySingleton(); //生成
}
return lazySingleton;
}
}
- 这样的写法就是最基础的懒汉式版本,但同时会又有另外一个问题,现在假象有一个线程 A 运行到了生成之前,对,想象成这个线程读这个A只读了一半,还没读到 new 呢,然后另外一个线程 B 悄咪咪的到了检查这一步,一看,行,原来还没有哥们new 啊,然后顺理走到了里面 new 的这一步,这个时候 A 已经创建了单例 A 。但是这个线程 B 也会创建单例 B。再如果这个线程 A 的手脚比较快的话,就直接返回给了系统这个单例 A ,然后我们友善的线程 B 说不对啊,我也辛苦了半天,肯定也会返回给系统这个单例 B。这样很显然,我们一个系统就同时存在了单例 A 和单例 B,故不符合单例模式的设计原则。
- 所以我们有下面这种写法
package com.try1.design.creational.singleton;
/**
* 懒汉式双重检查
*/
public class LazyDoubleCheckSingleton {
//volatile 是为了防止重排序的发生。
private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton=null;
private LazyDoubleCheckSingleton(){
}
public synchronized static LazyDoubleCheckSingleton getInstance(){
if (lazyDoubleCheckSingleton==null){ //1,是为了给后来的哥们看的,是否还可以去竞争
synchronized (LazyDoubleCheckSingleton.class){ //2,这里只能有一个哥们进去
if(lazyDoubleCheckSingleton==null){ //3,给第一批进去的哥们看是否还有机会了
lazyDoubleCheckSingleton=new LazyDoubleCheckSingleton(); //4,相亲中
}
}
}
return lazyDoubleCheckSingleton;
}
}
- 这种写法名为双重检查锁-懒汉式,这样的写法是特地对上述那种情况进行的设计。
- 现假想有现在这样一种情况,假设这是一个大型相亲现场,那姑娘俏啊,感情一大群年轻小伙唰一下就过来了,然后到了 1 这个点,这个时候女方父亲一看,这不行啊 ,我闺女一下怎么面对这么一大群人,kua一下,就把门给关上了,然后再门口坐着了,然后立了个牌子,大意就是我闺女还没被拐走,对应1这个 flag ,但是可能关门的速度不够快,还是有几个速度快的小伙挤进去了,但是里面的话,女方母亲还在姑娘房门前呢,看这几个年轻人这么努力,想了想,好吧,你们可以一个一个进去跟我闺女聊,就,随机放了一个年轻人进去了,然后也立了个跟女方父亲一样的牌子,对应 3 这个 flag 。再假设好吧,这个随机小伙还挺不错,进去一下子就获取了姑娘的芳心(这还是挺好啊),然后母亲一看女儿没了,伤心之余还是把门口的牌子,改成了闺女没了,母亲门口第一批进去的小伙一看,还是缘分不够啊,一下人就走完了,外面老父亲也听到了这个噩耗,伤心之余还是没忘记把门口的牌子改成了闺女没啦,门口小伙,一看也走完了。之后有想法的年轻人,一过来看到的就是这个牌子,还有老父亲要杀人的眼神,于是就溜了溜了。
- 咳咳,这样就确保了只会有一个小伙子和姑娘好上了,符合了单例模式的原则,和之前写法相比,改进的是,确保了检查的人就是最后创建的人,就是多了母亲这个角色,不会让自己闺女在有了男朋友的情况下,还让她和别的小伙相亲。
- 线程测试方法
package com.try1.design.creational.singleton;
public class T implements Runnable {
@Override
public void run() {
LazySingleton lazySingleton=LazySingleton.getInstance();
System.out.println(Thread.currentThread().getName()+" "+lazySingleton);
// ContainerSingleton.putInstance("object",new Object());
// Object instance=ContainerSingleton.getInstance("object");
// System.out.println(Thread.currentThread().getName()+" "+instance);
}
}
测试
// LazySingleton lazySingleton=LazySingleton.getInstance();
// Thread t1=new Thread(new T());
// Thread t2=new Thread(new T());
// t1.start();
// t2.start();
// System.out.println("program end!");
2,饿汉式
- 按照文本意思也可以这么理解,来者不拒,我先创建好,你要我就给你。
package com.try1.design.creational.singleton;
import java.io.Serializable;
public class HungrySingleton {
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton=new HungrySingleton();
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
- 这个算是最基础的版本,这个版本在于对序列化和反射都没有控制。
/*
序列化测试
*/
HungrySingleton instance=HungrySingleton.getInstance();
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file=new File("singleton_file");
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
HungrySingleton newInstance= (HungrySingleton) ois.readObject();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance==newInstance);
- 这里返回的就不是同一个对象了,因为在利用序列化的时候,判断产生的是另外一个对象。
- 我们可以让类实现序列化接口来防止序列化生成的不是同一个对象
防止序列化破坏后的版本
package com.try1.design.creational.singleton;
import java.io.Serializable;
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton;
private static boolean flag=true;
static{
hungrySingleton=new HungrySingleton();
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
//重写readResolve方法,使得判断返回同一个对象
private Object readResolve(){
return hungrySingleton;
}
}
再来是反射的一个问题,我可以通过反射来创建一个新的类,这个类因为不走里面的方法同样也是不同的类。
/*
反射攻击测试
*/
Class objectClass=HungrySingleton.class;
Constructor constructor=objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton instance=HungrySingleton.getInstance();
//用来修改反射内部的静态常量
// Field flag=instance.getClass().getDeclaredField("flag");
// flag.setAccessible(true);
// flag.set(instance,true);
HungrySingleton newInstance= (HungrySinglet on) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance==newInstance);
这里的 flag 是用作判断的一个参数,可以结合下方的单例类来看
package com.try1.design.creational.singleton;
import java.io.Serializable;
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton;
private static boolean flag=true;
static{
hungrySingleton=new HungrySingleton();
}
private HungrySingleton(){
if (hungrySingleton!=null){
throw new RuntimeException("单例构造器禁止反射调用!");
}
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
//重写readResolve方法,使得判断返回同一个对象
private Object readResolve(){
return hungrySingleton;
}
}
3,Enum枚举类
这个类就很厉害了,上面我们都是手动添加各种配置来防御序列化和反射,这货就直接自带了。
package com.try1.design.creational.singleton;
public enum EnumInstance {
INSTANCE{
protected void printTest(){
System.out.println("Geely Print Test");
}
};
protected abstract void printTest();
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance(){
return INSTANCE;
}
}
同样给出测试代码
EnumInstance instance=EnumInstance.getInstance();
instance.setData(new Object());
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file=new File("singleton_file");
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
EnumInstance newInstance= (EnumInstance) ois.readObject();
System.out.println(instance.getData());
System.out.println(newInstance.getData());
System.out.println(instance==newInstance);
- 序列化测试截图
- 反射测试截图
大概意思是不能反射这个类。
三,总结
单例模式,作为最容易理解,最常见,但是也最难用好的一个设计模式,使用的时候一定要把各种可能的情况想到,然后根据实际需要来选择写法;