单例模式

单例模式

单例模式: java中最简单的设计模式之一,这种类型的设计模式述语创建者模式,它提供了一种创建对象的最佳方式

这种模式只涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

特点

  • 单例类: 只能创建一个实例的类
  • 访问类:使用单例类

实现

两种实现方法

  • 饿汉式:类加载就会创建该单例的对象
  • 懒汉式:加载类不会导致该实例对象被创建,首次使用该对象时才会创建

饿汉式

Singleton是在类加载的时候创建的对象,饿汉式存在内存浪费问题

静态变量方式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {

//私有化构造方法
private Singleton() {
}
//本类中创建对象
private static Singleton INSTANCE = new Singleton();

//暴露一个公共方法对外界获取对象
public static Singleton getInstance() {
return INSTANCE;
}
}

静态代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
//私有化构造方法
private Singleton() {}

//声明Singleton变量
private static Singleton instance;

static{
instance = new Singleton();
}
public static Singleton getInstance(){
return instance;
}
}

懒汉式

方式一(双重检查方式锁)

​ 在多线程情况下会出现空指针的情况,出现问题原因是JVM在实例化对象时会进行优化指令重排序操作,解决双重检查锁模式带来的空指针问题,需要加上volatile关键字,可与保证指令可见性和有序性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton {
private Singleton() {
}

//volatile 防止指令重排
private static volatile Singleton instance;

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.instance) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

方式二(静态内部类)

​ 静态内部类单例方式由内部类创建,由于JVM在加载外部类过程中,是不会加载内部类的,只有内部类的属性方法被调用时才会被加载,并且初始化其静态属性,静态属性由于被static修饰,保证是会被实例化一次,并且严格保证实例化顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
//私有化构造器
private Singleton() {}

//在静态内部类中创建外部类对象
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}

//对外提供获取实例对象方法
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}

静态内部类单例模式是一种优秀的设计模式,是开源项目中比较常用的一种单例模式,在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间浪费

方式三(枚举方式)

枚举类实现单例模式是极力推荐的单例模式,因为枚举类是线程安全的,并且只会加载一次,设计者充分利用枚举这个铽行来实现单例模式,枚举写法十分简单,并且枚举类型是所有单例模式中唯一一个不会被破坏的模式

枚举方式属于饿汉式

1
2
3
public enum Singleton {
INSTANCE;
}

破坏单例模式

两种方式破坏单例模式

  • 序列化与反序列化
  • 反射

序列化反序列化

序列化可以破坏单例模式,但是枚举类型可以防止单例模式被破坏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Demo04 {

public static void main(String[] args) throws Exception {
Singleton singleton1 = getObj();
Singleton singleton2 = getObj();
System.out.println(singleton1 == singleton2);
}


public static Singleton getObj() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Users\\xss\\Desktop\\Singleton\\obj.txt"));
Singleton singleton = (Singleton) ois.readObject();
ois.close();
return singleton;
}


public static void writeObj() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Users\\xss\\Desktop\\Singleton\\obj.txt"));
oos.writeObject(Singleton.getInstance());
oos.close();
}
}

image-20230525143452978

对枚举类型进行序列化反序列化,得到的还是相同的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Demo04 {

public static void main(String[] args) throws Exception {
writeObj();
shejimoshi.singleton.Demo03.Singleton singleton1 = getObj();
shejimoshi.singleton.Demo03.Singleton singleton2 = getObj();
System.out.println(singleton1 == singleton2);
}


public static shejimoshi.singleton.Demo03.Singleton getObj() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Users\\xss\\Desktop\\Singleton\\obj.txt"));
shejimoshi.singleton.Demo03.Singleton singleton = (shejimoshi.singleton.Demo03.Singleton) ois.readObject();
ois.close();
return singleton;
}


public static void writeObj() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Users\\xss\\Desktop\\Singleton\\obj.txt"));
oos.writeObject(shejimoshi.singleton.Demo03.Singleton.INSTANCE);
oos.close();
}
}

image-20230525143400324

暴力反射破坏单例模式

反射会导致单例模式被破坏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo05 {
public static void main(String[] args) throws Exception{
Class clazz = Singleton.class;
Constructor cons = clazz.getDeclaredConstructor();
//破解私有化
cons.setAccessible(true);

//获取对象
Singleton singleton1 = (Singleton) cons.newInstance();
Singleton singleton2 = (Singleton) cons.newInstance();

System.out.println(singleton2 == singleton1);
}
}

image-20230525145222249

解决单例模式被破坏的问题

  • 序列化、反序列化破坏单例模式解决方案

    Singleton类中添加**readResolve()**方法,反序列化时被调用,如果定义了这个方法,就会返回这个方法的值,如果没有定义,返回的就是新对象的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class Singleton implements Serializable {
    //私有化构造方法
    private Singleton() {}

    //声明Singleton变量
    private static Singleton INSTANCE;

    static{
    INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){
    return INSTANCE;
    }
    public Object readResolve(){
    return INSTANCE;
    }
    }

    此时读取出来的对象时单例的,没有破坏单例模式

    image-20230525150629171

JDK 单例模式使用

Runtime

Runtime类使用饿汉式实现单例模式

JDK源码分析

1
2
3
4
5
6
7
8
9
10
11
12
public class Runtime {

//私有化实例对象
private static Runtime currentRuntime = new Runtime();

//获取单例对象
public static Runtime getRuntime() {
return currentRuntime;
}

//私有化构造方法
private Runtime() {}