单例模式是一种非常常见的设计模式, 在应用这个模式时,单例对象的类必须保证只有一个实例存在。 许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。
通俗来说就是一个类只有一个实例,并且由类自行实例化。
单例设计模式的核心思想:
1.构造器私有化
2.提供一个静态变量用于保存对象实例
3.提供一个外部访问对象的方式
其中对于步骤3,莫过于两种方式,静态变量公有化或者提供一个获取对象的方法,对于这两种不同的方式也就衍生出了饿汉式和懒汉式,饿汉式和懒汉式的区别在于创建实例化的时期,由此导致的线程安全方面的不同。
饿汉式(积极创建,线程安全)
顾名思意,一调用该类时便不管三七二十一直接创建实例,不管能不能用上。显然,在类加载时期便完成了对象的创建。此后所有调用者调用的都是已经创建好的这个实例,定然不会出现线程安全问题。
以下来说说饿汉式的具体实现。(直接公有化变量)
第一种方式:直接公有化变量。
public class Singleton1 {
//2.提供静态变量保存创建的实例;3.变量公有化可直接访问。
public static Singleton1 INSTANCE = new Singleton1();
//1.构造器私有化
private Singleton1() {
}
}
第二种方式:枚举实现
public enum Singleton2 {
INSTANCE
}
第三种方式:通过静态代码块加载实现。(适合于创建复杂对象,当创建对象需要读取配置文件时可用这种方式)
public class Singleton3 {
public static Singleton3 INSTANCE;
private Singleton3(String info) {
}
static {
Properties prop = new Properties();
try {
prop.load(Singleton3.class.getClassLoader().getResourceAsStream("aa.properties"));
} catch (IOException e) {
e.printStackTrace();
}
INSTANCE = new Singleton3(prop.getProperty("info"));
}
}
总结:饿汉式在类加载时期完成对象的创建,是线程安全的。
懒汉式(延时创建,线程不安全)
顾名思意:在使用类时不会直接创建,只有在具体的发出对象调用请求(使用获取对象的方法)时才进行创建。
具体实现如下:
public class Singleton4 {
//2.提供静态变量保存实例对象
private static Singleton4 instance;
//1.构造器私有化
private Singleton4() {
}
//3.对外提供获取对象的方法
public static Singleton4 getInstance(){
if(instance == null) {
instance = new Singleton4();
}
return instance;
}
}
很显然,改代码是存在线程安全问题的。在多线程情况下,当instance为空时,可能会有多个线程通过if(instance == null),导致创建出多个对象。
线程安全版本1:(通过加锁实现,DCL(Double Check Lock)+volatile)
public class Singleton5 {
private static volatile Singleton4 instance;
private Singleton5() {
}
public static Singleton5 getInstance(){
if(instance == null){
synchronized(Singleton5.class){
if(instance == null) {
instance = new Singleton4();
}
}
}
return instance;
}
}
//说明:1.为什么要加DCL,同步锁外判断,为避免在实例已经创建的情况下每次获取实例都加锁取,影响性能;
锁内判断,考虑多线程情况下,两个以上线程都已经运行至同步锁处,也就是都已经判断变量为空,如锁内不再次判
断,会导致实例重复创建。
2.为什么要加volatile,instance = new Singleton4()这句话由三条字节码指令完成,
其具体步骤:1.分配内存空间,2.初始化,3.对引用赋值。由于指令重排序的存在,这三个步骤可能并不是按
顺序执行。加volatile能够避免指令重排序。
线程安全版本2:通过内部类实现
public class Singleton6 {
private Singleton6() {
}
//说明:外部类的加载不会导致内部类的加载。
private static class Inner{
private static Singleton6 instance = new Singleton6();
}
//在调用该方法时才会导致内部类的加载。
public static Singleton6 getInstance() {
return Inner.instance;
}
}
总结:懒汉式不会在加载类时创建实例对象,会在发出对象调用请求时才进行实例化。可能会导致线程安全问题,通过加锁和内部类可解决。