什么是单例模式?
一个类只能创建一个对象,堆内存中就只开辟一个空间,有效减少内存占用空间
如何设计单例模式?
- 创建一个对象是调用该类的构造方法,因此应该控制构造方法不能让别人随意调用构造方法创建对象,因此设计私有的构造方法。
- 由于私有的构造方法后,对象不能随意创建,因此我们需要在类的内部进行创建唯一一个对象,否则别人就拿不了对象啦。
- 需要考虑在类内部成员中(属性、块、构造方法、方法)哪个位置创建该类的对象合适,由于块是没有返回值,因此我们创建的对象不能传递给外面,因此不能使用;由于构造方法本身就是创建对象,如果在构造方法中创建对象会造成栈内存溢出,相当于一直调用构造方法创建对象,对象永远创建不完,堆内存中的空间比栈内存稍大,执行构造方法在栈内存中临时开辟空间执行,因此栈内存溢出,而且构造方法以及私有了;由于方法每次执行一次都在栈内存中开辟一个临时的执行空间执行创建对象,这样不能保证创建的对象唯一。
- 综上所述,块,构造方法,块这些成员变量都不满足单例的条件,因此可以考虑属性,属性中直接给引用赋值创建对象显然不行,还会造成栈内存溢出,一个对象不断的创建(调用构造方法,临时在栈内存开辟空间执行),永远都创建不完,因此需要加上static修饰属性,保证该属性只有一份,而且属性应该私有,不能让外部访问,安全。
- 设计好唯一一个对象后,由于外部无法使用创建的对象,我们需要设计一个方法返回给外部,public修饰、static静态修饰,为了外部能通过类名调用方法,而不用创建对象,将唯一一个单例对象传递出去使用。
单例类:
public class Singleton {
//饿汉式(立即加载方式)
//属性私有防止外部直接访问属性,不安全,静态保证该对象存在唯一一个
private static Singleton singleton=new Singleton();
//构造方法私有,保证该对象的创建不能外部创建
private Singleton(){}
//公有静态修饰是确保外部能够直接通过类名访问,不用通过创建对象
public static Singleton getInstance(){
return singleton;
}
}
测试示例:
public class TestMain {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
System.out.println(instance==instance1);
}
}
结果:
单例模式内存图:
以上单例设计,在类加载的时候,单例对象已经创建完毕,如果考虑到性能问题,整个系统在执行过程中不使用,则会一直占用内存,浪费内存空间
优化设计:
public class Singleton {
//懒汉式(延迟加载方式)
//属性私有防止外部直接访问属性,不安全,静态保证该对象存在唯一一个
private static Singleton singleton;
//构造方法私有,保证该对象的创建不能外部创建
private Singleton(){}
//公有静态修饰是确保外部能够直接通过类名访问,不用通过创建对象
public static Singleton getInstance(){
//如果方法没有调用则不进行创建,什么时候用再创建单例对象
if (singleton==null){
singleton=new Singleton();
}
return singleton;
}
}
测试结果:
懒汉式的延迟加载创建单例对象,在多个线程并发执行,一个线程刚判断完是null,此时还没调用方法创建对象,则另外一个线程也进行判断是null,此时会造成一个空间进行两次创建对象赋值的过程。
设计优化:加入线程锁,双重判断
public class Singleton {
//懒汉式(延迟加载方式)
//属性私有防止外部直接访问属性,不安全,静态保证该对象存在唯一一个
private static Singleton singleton;
//构造方法私有,保证该对象的创建不能外部创建
private Singleton(){}
//公有静态修饰是确保外部能够直接通过类名访问,不用通过创建对象
public static Singleton getInstance(){
//先判断该对象是否被创建
if (singleton==null){
//如果没有创建,则用线程锁锁住当前类模板(防止创建对象),其他线程不能操作
synchronized (Singleton.class){
//再进行判断对象是否已经被赋值(双重判断)
if (singleton==null){
//如果对象还未创建,则创建对象
singleton=new Singleton();
}
}
}
return singleton;
}
}
测试结果:
上面设计解决了性能问题,但对象的开辟和赋值的过程中可能会产生指令重排序,JVM虚拟机可能会提高性能,对对象的加载和赋值产生顺序颠倒,导致先赋值后加载,导致赋值到的对象是空的,因此属性加上volatile,可以不被JVM指令重排序。
public class Singleton {
//懒汉式(延迟加载方式)
//属性私有防止外部直接访问属性,不安全,静态保证该对象存在唯一一个
//volatile防止虚拟机指令重排序
private volatile static Singleton singleton;
//构造方法私有,保证该对象的创建不能外部创建
private Singleton(){}
//公有静态修饰是确保外部能够直接通过类名访问,不用通过创建对象
public static Singleton getInstance(){
//先判断该对象是否被创建
if (singleton==null){
//如果没有创建,则用线程锁锁住当前类模板(防止创建对象),其他线程不能操作
synchronized (Singleton.class){
//再进行判断对象是否已经被赋值(双重判断)
if (singleton==null){
//如果对象还未创建,则创建对象
singleton=new Singleton();
}
}
}
return singleton;
}
}