单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点!
单例模式应该是23种设计模式中最简单的一种模式了。它有以下几个要素:
- 私有的构造方法
- 指向自己实例的私有静态引用
- 以自己实例为返回值的静态的公有的方法
单例模式5种写法-- 饿汉式单例,懒汉式单例,双层校验锁,静态内部类,枚举。
饿汉式单例在单例类被加载时候,就实例化一个对象交给自己的引用;
懒汉式在调用取得实例方法的时候才会实例化对象。
饿汉式单例
饿汉法就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建,好处是编写简单,但是无法做到延迟创建对象。
/** * 饿汉式单例 */ public class Singleton { // 将自身实例化对象设置为一个属性,并用 static、final 修饰 private static final Singleton singleton = new Singleton(); // 构造方法私有化 private Singleton (){} // 静态方法返回该实例 public static Singleton getInstance(){ return singleton; } }
懒汉式单例
懒汉式单例模式,线程不安全,由私有构造器和一个公有静态工厂方法构成,在工厂方法中对singleton进行null判断,如果是null就new一个出来,最后返回singleton对象
这种方法可以实现延时加载(lazy landing),但是有一个致命弱点:线程不安全。如果有两条线程同时调用getSingleton()方法,就有很大可能导致重复创建对象。
/** * 懒汉式单例 */ public class Singleton { // 将自身实例化对象设置为一个属性,并用 static 修饰 private static Singleton singleton; // 构造方法私有化 private Singleton() {} // 静态方法返回该实例 public static synchronized Singleton getSingleton() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
双层校验锁
双重非空判断,new对象前加一次锁。
volatile关键字,由于volatile关键字屏蔽了虚拟机中一些必要的代码优化,所以运行效率并不是很高,因此建议没有特别的需要不要使用,而volatile关键字可以防止指令重排。
/** * 双层校验锁 */ public class Singleton { // 将自身实例化对象设置为一个属性,并用 volatile 修饰 private volatile static Singleton singleton; // 构造方法私有化 private Singleton() {} // 静态方法返回该实例 public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
静态内部类
由于静态内部类跟外部类是平级的,所以外部类加载的时候不会影响内部类,因此实现了lazy loading,同时也是利用静态变量的方式,使得INSTANCE只会在SingletonHolder加载的时候初始化一次,从而保证不会有多线程初始化的情况,因此也是线程安全的。
/** * 静态内部类 */ public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
枚举
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,是线程安全的,但是很少见使用。
/** * 枚举 */ public enum Singleton { INSTANCE; public void whateverMethod() {} }
饿汉式和懒汉式区别
1.从名字上来说,饿汉和懒汉,饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,而懒汉模式,只有当调用getInstance的时候,才会去初始化这个单例。
2.饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,懒汉式本身是非线程安全的。
3.饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
4.在Java中,饿汉式单例要优于懒汉式单例。
单例模式的优点:
- 在内存中只有一个对象,节省内存空间。
- 避免频繁的创建销毁对象,可以提高性能。
- 避免对共享资源的多重占用。
- 可以全局访问。
适用场景:
由于单例模式的以上优点,所以是编程中用的比较多的一种设计模式。以下是一些适合使用单例模式的场景:
- 需要频繁实例化然后销毁的对象。
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
- 有状态的工具类对象。
- 频繁访问数据库或文件的对象。
- 以及其他我没用过的所有要求只有一个对象的场景。
单例模式注意事项:
- 只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。
- 不要做断开单例类对象与类中静态引用的危险操作。
- 多线程使用单例使用共享资源时,注意线程安全问题。