何为单例模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
思路
如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private
,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。然后再外部通过类名直接调用getIntance方法获取类的实例。因此需要将getInstance方法设置为static,而get方法里边要调用私有的对象变量,因此对象变量也要设置成static。
因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法
以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的
。
饿汉式 vs 懒汉式
饿汉式:
- 特点:
立即加载
,即在使用类的时候已经将对象创建完毕。 - 优点:实现起来
简单
;没有多线程安全问题。 - 缺点:当类被加载的时候,会初始化static的实例,静态变量被创建并分配内存空间,从这以后,这个static的实例便一直占着这块内存,直到类被卸载时,静态变量被摧毁,并释放所占有的内存。因此在某些特定条件下会
耗费内存
。
public class Singleton {
private Singleton(){}
private static Singleton singleton = new Singleton();
public static Singleton getInstance(){
return singleton;
}
}
懒汉式(线程不安全):
- 特点:
延迟加载
,即在调用静态方法时实例才被创建。 - 优点:实现起来比较简单;当类被加载的时候,static的实例未被创建并分配内存空间,当静态方法第一次被调用时,初始化实例变量,并分配内存,因此在某些特定条件下会
节约内存
。 - 缺点:在多线程环境中,这种实现方法是完全错误的,
线程不安全
,根本不能保证单例的唯一性。
public class Singleton {
private Singleton(){};
private static Singleton singleton ;
public static Singleton getInstance(){
if(singleton == null){
singleton =new Singleton();
}
return singleton;
}
}
懒汉式(线程安全版):
对象初始化的创建是由很多步骤完成的,比如分配空间、初始化、调用构造器等,是一个复杂的过程,有可能上一个进程进去,已经调用了new Singleton,但new对象的过程并没有执行完成,就已经return退出,如果下一个进程再进来,此时单例已经不为空,会返回之前一个进程已经return的单例对象,但是有可能此对象并没有完成调用构造器的初始化过程,因此需要加入volatile关键字,避免指令重排。
volatile
是 Java 中的一个关键字,用于修饰变量,主要用于确保变量在多线程环境下的可见性和禁止指令重排。它在多线程编程中用于解决共享变量的一些并发访问问题。具体来说,
volatile
关键字提供了以下两个主要特性:
可见性(Visibility): 当一个变量被声明为
volatile
,任何一个线程对这个变量的修改会立即被刷新到主内存中,同时,其他线程在读取该变量时会直接从主内存中读取,而不是从线程的本地缓存中读取。这确保了对volatile
变量的修改对其他线程是可见的,避免了线程之间的数据不一致问题。禁止指令重排(Prevent Instruction Reordering):
volatile
变量的读写操作会禁止指令重排,保证在代码执行时不会发生意外的顺序变化,从而确保线程间操作的正确性。
public class Singleton {
private Singleton(){};
private static volatile Singleton singleton ;
public synchronized static Singleton getInstance(){
if(singleton == null){
if(singleton == null){
singleton =new Singleton();
}
return singleton;
}
}
}
错误方式:
不可以通过这种方式写单例模式,因为在构造器中,使用new新建实例,而新建的实例又会自己调用构造器,行程了无限递归循环调用,会导致StackOverflowException错误
public class Singleton {
private static Singleton singleton ;
private Singleton(){
singleton =new Singleton();//这里会循环调用自己,导致栈溢出
}
public static Singleton getInstance(){
if(singleton == null){
return Singleton();//开始无限调用构造器
}
return singleton;
}
}