引言:
单例模式是软件开发中最常见的设计模式之一,顾名思义,整个系统中只有一个实例对象能被获取和使用。
它的要点有三:
- 一个类只能有一个实例——私有化构造函数
- 这个实例由这个类自行创建——类的静态变量保存这个唯一实例
- 必须自行向系统提供这个实例——1.直接暴露 2.静态方法get到它
(不管是饿汉式还是懒汉式都体现以上三点)
两种常见形式
- 饿汉式:不管如何都会直接创建实例对象(无线程安全问题)
- 懒汉式:延迟创建实例对象(会面临线程安全问题)
一、饿汉式(三种方式)
饿汉式有三种方法,分别是直接实例化,枚举类型,和静态代码块。饿汉式不涉及线程安全问题,它都是加载类的时候初始化对象。
第一版:直接实例化饿汉式
public class Singleton1 {public static final Singleton1 INSTANCE = new Singleton1(); private Singleton1(){ //私有化构造器}
}
Test:
public class Singleton1Test {public static void main(String[] args) {Singleton1 singleton1 = Singleton1.INSTANCE;System.out.println(singleton1);}}
第二版:枚举类型
枚举类型表示的是该类型的对象是有限的几个,我们可以先定为1个:
public enum Singleton2 {INSTANCE;
}
Test方式与上一种一样:
public class Singleton2Test {public static void main(String[] args) {Singleton2 Singleton2 = Singleton2.INSTANCE;System.out.println(Singleton2);}
}
第三版:静态代码块
这种方法就是将第一版中的静态对象实例化过程写在静态代码块中,看起来明显复杂很多,不如第一种简洁明了。它的应用场景是当我们的构造函数是带参的,参数写在config.properties中:
如下:
name=JakeLin
我们可以在静态代码块中加载这类配置:
public class Singleton3 {public static final Singleton3 INSTANCE ;private String name;static{Properties pro = new Properties();try {pro.load(Singleton3.class.getClassLoader().getResourceAsStream("config.properties"));} catch (IOException e) {e.printStackTrace();}INSTANCE = new Singleton3(pro.getProperty("name"));}private Singleton3(String info){this.name = name;}public String toString() {return "Singleton3 [name=" + name + "]";}}
Test:
public class Singleton3Tset {public static void main(String[] args) {Singleton3 singleton3 = Singleton3.INSTANCE;System.out.println(singleton3);}
}
二、懒汉式
懒汉式觉得饿汉太过头了,啥时候加载类都new一个对象,但有时候我们只需要调用这个类中的一个方法,是不想要创建这个对象的,所以懒汉提出不再在加载类的时候new实例对象,而是提供一个静态方法,来获取这个实例对象,你要创建对象,就去调用这个方法。
public class Singleton4 {static Singleton4 instance; //提供静态实例对象private Singleton4(){ //私有化构造器}public static Singleton4 getInstance(){if(instance == null){ //保证单例instance = new Singleton4();}return instance;}
}
在单线程的情况下这个方法并没有问题,但是如果是多线程,就会出现线程安全问题。
线程1进来时,会发现实例为空,那么会去创建一个实例,这时候线程2进来了,线程1还没创好呢,线程2发现实例也还是空的,那么就会也创建一个实例,结果呢,导致两个线程创建了两个不同的实例,违背了单例的原则,这就是懒汉的线程安全问题。
怎么解决呢?我们很容易考虑到用加锁的方式来解决:
public class Singleton4 {static Singleton4 instance;private Singleton4(){}public static Singleton4 getInstance(){if (instance == null) { // 加快效率,前面抢线程判断一下就好,后面有实例了就直接跳过// 解决安全问题synchronized (Singleton4.class) { //锁对象if(instance == null){instance = new Singleton4();}}}return instance;}
}
这样写似乎显得有点太复杂, 我们还可以用另一种方法,在内部类被加载和初始化时,才创建对象,解决懒汉的线程安全问题:
public class Singleton5 {private Singleton5(){}public static class inner{ // 静态内部类private static Singleton5 INSTANCE;}public static Singleton5 getInstance(){ // 调用此方法会创建静态内部类的对象return inner.INSTANCE;}
}
因为静态内部类不会随着外部类被加载和初始化而自动加载和初始化,而是要单独去加载和初始化的。因为是在内部类加载和初始化时创建对象的,因此线程是安全的。
总结:
以上就是饿汉式与懒汉式两种单例模式,饿汉式不需要考虑线程安全问题,而懒汉式需要。饿汉式在加载类的时候创建实例,而懒汉式是调用方法时创建实例。