0、前言
考察:并发?类加载?序列化?
什么是单例模式:保证一个类只有一个实例,并且提供一个全局可以访问的入口。
为什么要用单例模式:
- 节省内存,节省计算。
- 保证结果的正确
1、单例模式的应用场景
- 无状态的工具类:日志工具,字符创工具
- 全局信息类:全局计数,环境变量
2、单例模式的5种写法
【饿汉式】
public class Singleton01 { private static Singleton01 singleton = new Singleton01(); private Singleton01() { } public static Singleton01 getInstance() { return singleton; } }
添加了静态代码块——把类加载这部分逻辑放到静态中
public class Singleton02 { private static Singleton02 singleton02; static { singleton02 = new Singleton02(); } private Singleton02(){} public Singleton02 getInstance(){ return singleton02; } }
以上两种都是类初始化的时候就直接加载了。但是如果这个类从始至终都没有使用过的话就会造成内存浪费。
【懒汉模式】 :判断类不为空的话就初始化一个对象
public class Singleton03 { private static Singleton03 singleton03; private Singleton03(){} public static Singleton03 getInstance(){ if (singleton03 == null){ singleton03 = new Singleton03(); } return singleton03; } }
上面那个线程不安全:多线程情况下回创建多个实例
public class Singleton03 { private static Singleton03 singleton03; private Singleton03(){} public static synchronized Singleton03 getInstance(){ if (singleton03 == null){ singleton03 = new Singleton03(); } return singleton03; } }
以上代码 多线程情况下效率不高
public class Singleton04 { private static Singleton04 singleton04; private Singleton04(){} public static Singleton04 getInstance(){ if (singleton04 == null){ synchronized (Singleton04.class) { singleton04 = new Singleton04(); } } return singleton04; } }
提高了效率 但是有可能会多实例
public class Singleton05 { //线程安全 private static volatile Singleton05 singleton05; private Singleton05(){} public static Singleton05 getInstance(){ if (singleton05 == null){ //去掉后导致串行 synchronized (Singleton05.class) { if (singleton05 == null) { //去掉后 singleton05 = new Singleton05(); } } } return singleton05; } }
双重检查 能够解决上面破坏实例化的问题。
JVM 在这个方法中做了几件事情:顺序可能是1-2-3 或者 1-3-2
1、给singleton分配内存空间
2、调用singleton的构造函数等来初始化singleton
3、将singleton对象指向分配的内存空间(执行完这一步的话singleton就不是null了)
volatile关键字要注意下哦!能够防止上面说的重排序而导致的报错
public class Singleton06 { //静态内部类的写法 private Singleton06(){} private static class SingletonInstance{ private static final Singleton06 singleton06 = new Singleton06(); } public static Singleton06 getInstance(){ return SingletonInstance.singleton06; } }
public enum Singleton07 { INSTANCE; public void whateverMethod(){} }
枚举主要是为了防止反序列化 和 反射来导致多个实例被构造出来。
参考https://www.cnblogs.com/cjn123/p/12159536.html
3、枚举类写法的优点
《Effective Java》 用私有构造器或者枚举类型强化SingleTon(单例)属性
单例(singleton)就是一个只实例化一次的类。使类成为单例可能会使它的测试变得困难,因为除非它实现了作为其类型的接口,否则不可能用模拟实现来代替这个单例。下面是几种实现单例的方法:
1、共有静态成员是final类型
public class SingletonEffective01 { public static final SingletonEffective01 INSTANCE = new SingletonEffective01(); private SingletonEffective01(){} public void lTB(){} }
私有构造器之后执行一次,实例化Elvis.INSTANCE属性,由于缺少公有(public)的或者受保护(protected)的构造器,所以Elvis一旦被实例化,就只会存在一个Elvis的实例(注:反射是可以实现多次调用私有的构造器,若需要抵御这种攻击,则可以修改私有构造器,让它在被创建第二个实例时,抛出异常,或直接返回第一个实例对象)。
2、公有的成员是一个静态方法。
public class SingletonEffective02 { public static final SingletonEffective02 INSTANCE = new SingletonEffective02(); private SingletonEffective02(){} public static SingletonEffective02 getInstance(){return INSTANCE;} //新增了这个方法 public void lTB(){} //为防止反序列化时又创建了一个新的实例,需要在反序列化是直接返回新的数据 //1、防止反序列化获取多个对象的漏洞。 //2、无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。 //3、实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象 private Object readResolve(){ return INSTANCE; } }
这个方式可以通过公有静态方法将类改为非单例,但用户代码不需要改变。
为了使利用这其中一种方法实现的SingleTon类变成是可序列化的(Serializable),仅仅在申明上加上“implements Serializable”是不够的。为了维护并保证SingleTon,必须申明所有实例都是瞬时的(transient),并提供一个readResolve方法,否者,每次反序列化一个序列化的实例时,都会创建一个新的实例,比如说,在我们的实例中,会导致“假冒的Elvis”。为了防止这种情况,要在Elvis类中加入readResolve方法
3、使用枚举实现单例
public class SingletonEffective03{ private SingletonEffective03(){} public static SingletonEffective03 getInstance(){ return Singleton.INSTANCE.getInstance(); } private enum Singleton{ INSTANCE; private SingletonEffective03 singleton; //JVM会保证此方法绝对只调用一次 Singleton(){ singleton = new SingletonEffective03(); } public SingletonEffective03 getInstance(){ return singleton; } } }
4、引发的一些小知识点
final关键字:
volatile关键字: