0%

单例模式

单例模式

保证系统中一个类只有一个实例,并提供一个访问它的全局访问点,根据实例化时机的不同分为两种,饿汉式懒汉式

四大原则

  • 构造私有
  • 以静态方法或者枚举返回实例
  • 确保实例只有一个
  • 确保反序列化不会重复构建对象

饿汉模式(立即加载)

饿汉式单例在单例类被加载的时候就实例化一个对象交给自己的引用,其是线程安全

1
2
3
4
5
6
7
8
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}

懒汉模式(延迟加载)

懒汉式单例是在调用取得实例方法的时候才会实例化对象

非线程安全的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
private static Singleton instance = null;

private Singleton(){

}

public static Singleton newInstance(){
// 在需要的时候进行创建,如果已存在,直接返回之前的对象
if(instance == null){
instance = new Singleton();
System.out.println("创建单例实例");
}
return instance;
}
}

线程安全加同步锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SyncSingleton {
private static SyncSingleton instance = null;

private SyncSingleton(){

}

// 使用synchronized对方法进行加锁
public static synchronized SyncSingleton newInstance(){
// 在需要的时候进行创建,如果已存在,直接返回之前的对象
if(instance == null){
instance = new SyncSingleton();
System.out.println("创建实例");
}
return instance;
}
}

加锁范围比较大,运行效率低,导致下一个线程想要获取对象必须等上一个线程释放锁之后才可以

双重检查减少锁粒度

将同步内容放到if内部,提高了执行的效率,不必每次获取对象时都进行同步,只有第一次才同步,后续instance不会为null,使得不需要进行加锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TwiceCheckSingleton {
// volatile防止创建对象时发生指令重排,导致其他线程使用对象时造成空指针
private static volatile TwiceCheckSingleton instance = null;

private TwiceCheckSingleton(){

}

public static TwiceCheckSingleton newInstance(){
// 在需要的时候进行创建,如果已存在,直接返回之前的对象
if(instance == null){
synchronized(TwiceCheckSingleton.class){
if(instance == null){
instance = new TwiceCheckSingleton();
System.out.println("创建单例实例1");
}
System.out.println("创建单例实例2");
}
}
return instance;
}
}

使用静态内部类

通过类加载机制保证只创建一个单例对象,静态内部类只有在调用时才会加载,保证了单例的延迟初始化,不过如果序列化对象时,得到的结果是多例的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StaticOutterClassSingleton {
// 私有静态内部类,该静态内部类只会在newInstance()中使用
private static class SingletonHolder{
public static StaticOutterClassSingleton instance = new StaticOutterClassSingleton();
}

private StaticOutterClassSingleton(){

}

public static StaticOutterClassSingleton newInstance(){
return SingletonHolder.instance;
}
}

使用内部枚举类

静态内部类无法防止序列化和反射问题,可以使用枚举来实现单例模式

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

private enum EnumSin{
Singleton;

private EnumSingleton singleton;

// 枚举的构造方法是在类加载时被实例化的
private EnumSin(){
singleton = new EnumSingleton();
}

public EnumSingleton getInstance(){
return singleton;
}
}

public static EnumSingleton getInstance(){
return EnumSin.Singleton.singleton;
}

private EnumSingleton(){

}
}

特点

  • 单例类只有一个实例
  • 单例类必须自己创建自己的唯一实例
  • 单例类必须给所有其他对象提供这一实例

优缺点

懒汉式是用时间换空间,饿汉式是空间换时间

优点

  • 在内存只有一个对象实例,节省内存
  • 避免频繁创建销毁对象,提高性能
  • 避免对共享资源的多重占用
  • 可以全局访问

缺点

  • 扩展困难,单例方法是静态方法,无法重写
  • 隐式使用引起类结构不清晰
  • 导致程序内存泄露的问题

使用场景

  • 需要频繁实例化后销毁的对象
  • 创建一个对象需要消耗的资源过多或者时间过长,但又经常用到的对象
  • 资源共享的情况下,避免由于资源操作时导致的性能损耗

欢迎关注我的其它发布渠道