一、模式定义:
- 保证一个类只有一个实例,并且提供一个全局访问点
二、使用场景:
- 重量级对象,不需要多个实例,如:线程池,数据库连接池
三、类图:
四、不同方式创建的单例设计与区别
4.1、懒汉单例模式
懒汉单例模式的特点是延迟加载,只有在真正使用的时候,才开始实例化,用该方式创建单例对象时,对于线程安全问题和指令重排序导致的初始化并且引用赋值失败的问题,可以用以下2种方式来解决:
①、对于线程安全问题,可以使用【double check】加【锁】进行优化;
②、对于编译器(JIT),CPU可能对指令进行重排序,导致使用尚未初始化的单例实例,因此,需要通过对创建的单例对象添加volatile关键字进行修饰,来防止指令重排序。
懒汉单例模式实现单例的方式,如下所示:
class Service{
public static LazySingleton instance = null;
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
instance = LazySingleton.getInstance();
System.out.println(instance);
}
}).start();
new Thread(new Runnable() {
public void run() {
instance = LazySingleton.getInstance();
System.out.println(instance);
}
}).start();
}
}
/**
* 懒汉式:当第一次去使用Singleton对象的时候才会为其产生实例化对象的操作
* */
public class LazySingleton {
//volatile关键字可以保证这个对象的赋值顺序按照以下顺序来执行:
//1、分配空间,2、初始化,3、引用赋值
private volatile static LazySingleton instance = null;
private LazySingleton() {
}
/**
* 外部调用来获取单例对象的函数
*/
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class){
if (instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
上面代码的运行结果如下:
- volatile关键字可以在字节码层面保证这个对象的赋值顺序按照以下顺序来执行:1、分配空间,2、初始化,3、引用赋值。如果没有volatile关键字,可能在起始初始化的时候,拿到的是空对象。
4.2、饿汉单例模式
饿汉单例模式的特点是类加载的时候进行初始化,通过JVM的类加载机制来保证单例。饿汉单例模式实现单例的方式,如下所示:
public class HungrySingleton {
/**
* 在类的内部可以访问私有结构,所以可以在类的内部产生实例化对象
*/
private static HungrySingleton instance = new HungrySingleton();
/**
* private 声明构造
*/
private HungrySingleton() { }
/**
* 返回对象实例
*/
public static HungrySingleton getInstance(){
return instance;
}
}
4.3、静态内部类实现单例
静态内部类实现单例的特点是只有真正使用对应的类时,才会触发初始化,使用方式包括以下5种:
①、new操作;
②、访问静态属性;
③、访问静态方法;
④、用反射访问类;
⑤、初始化一个类的子类;
静态内部类实现单例的方式,如下所示:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ServiceInnerClass{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//通过反射方式创建InnerClassSingleton实例:
Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
//因为用private修饰的InnerClassSingleton的构造函数中进行了判断,防止反射方式创建这个对象实例,因此:
// ①、反射在加载InnerClassSingleton.class文件前,已经加载了InnerClassSingleton的静态内部类。
//②、加载静态内部类时,也加载了静态instance变量,进行初始化。
//③、因此,反射方式在创建实例时,会抛出运行时异常。
InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
//只能通过InnerClassSingleton.class的getInstance()函数来获取这个单例
InnerClassSingleton instance = InnerClassSingleton.getInstance();
}
}
class InnerClassSingleton {
//4、静态内部类,在加载外部类的时候,该类和该类中的静态变量被加载
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
//1、提供private修饰的私有构造方法,保证外部的类无法对该类进行初始化
private InnerClassSingleton(){
//5、进行判断,防止反射方式创建这个对象实例
if(InnerClassHolder.instance != null){
throw new RuntimeException("单例模式,不允许多个实例");
}
}
//2、只有在加载这个类的时候,才能拿使用这个静态函数。
public static InnerClassSingleton getInstance(){
//3、该静态函数内同时加载静态内部类,来初始化InnerClassSingleton对象
return InnerClassHolder.instance;
}
}
4.4、从序列化文件中读取一个类的实例
从序列化文件中读取一个类的实例实现单例的特点是该实例需要实现Serializable.interface接口,Serializable.interface接口的特点,如下所示:
从序列化文件中读取一个类的实例来实现单例的方式,如下所示:
import java.io.*;
import java.lang.reflect.InvocationTargetException;
class ServiceInnerClass{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
InnerClassSingleton instance = InnerClassSingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable"));
oos.writeObject(instance);
oos.close();
//将刚才的对象实例序列化之后,再进行反序列化。得到的实例相同。
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
InnerClassSingleton innerClassSingleton = (InnerClassSingleton) ois.readObject();
ois.close();
System.out.println(innerClassSingleton == instance);
}
}
public class InnerClassSingleton implements Serializable {
//序列化版本号。不加的话,如果序列化之后,进行修改类中的内容,则不能进行反序列化。
private static final long serialVersionUID = 42L;
//4、静态内部类,在加载外部类的时候,该类和该类中的静态变量被加载
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
//1、提供私有构造方法,保证外部的类无法对该类
private InnerClassSingleton(){
//5、进行判断,防止反射方式创建这个对象实例
if(InnerClassHolder.instance != null){
throw new RuntimeException("单例模式,不允许多个实例");
}
}
//2、只有在加载这个类的时候,才能拿使用静态方法。
public static InnerClassSingleton getInstance(){
//3、同时加载静态内部类,来初始化InnerClassSingleton对象
return InnerClassHolder.instance;
}
private Object readResolve() throws ObjectStreamException {
return InnerClassHolder.instance;
}
}
上面代码的运行结果如下:
五、单例模式的应用
-
JDK源码中Runtime.class,使用了饿汉单例模式
-
spring框架中的ReactiveAdapterRegistry.class,使用了懒汉单例模式:
六、JDK源码中,Serializable接口的应用
- 货币类Currency类