单例模式
# 需求
在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,显然,这种方式简化了在复杂环境下的配置管理。
- 确保一个类只有一个实例,并为整个系统提供一个全局访问点 (向整个系统提供这个实例)。
- 在内存中只有一个对象,节省内存空间;
- 避免频繁的创建销毁对象,可以提高性能;
- 避免对共享资源的多重占用,简化访问;
- 为整个系统提供一个全局访问点。
# 饿汉式:线程安全
在应用启动时,就加载。 主要原理: 在静态方法时就new出对象
// 饿汉式单例
public class Singleton1 {
// 指向自己实例的私有静态引用,主动创建
private static Singleton1 singleton1 = new Singleton1();
// 私有的构造方法
private Singleton1(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton1 getSingleton1(){
return singleton1;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
在线程访问单例对象之前就已经创建好了。再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。因此就说,饿汉式单例天生就是线程安全的。
# 懒汉式
// 懒汉式单例:线程不安全
public class Singleton2 {
// 指向自己实例的私有静态引用
private static Singleton2 singleton2;
// 私有的构造方法
private Singleton2(){}
// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton2 getSingleton2(){
// 被动创建,在真正需要使用时才去创建
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 线程安全的懒汉式单例
public class Singleton5 {
// 私有内部类,按需加载,用时加载,也就是延迟加载
private static class Holder {
private static Singleton5 singleton5 = new Singleton5();
}
private Singleton5() {}
public static Singleton5 getSingleton5() {
return Holder.singleton5;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
参考实现:cn.hutool.core.lang.Singleton
# 双重检验锁模式
/**
* 双重校验锁方式 创建单例
*
*/
public class Singleton {
private Singleton() {}
private volatile static Singleton myinstance;
public static Singleton getInstance() {
if (myinstance == null) { // 同步代码块外非空判断
synchronized (Singleton.class) {
if (myinstance == null) { // 同步代码块内非空判断
myinstance = new Singleton();
}
}
}
return myinstance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
1.给 instance 分配内存 2.调用 Singleton 的构造函数来初始化成员变量 3.将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
高并发情况下,cpu会对指令进行指令重排,变成了1-3-2,造成得到一个空对象的可能,因此需要变成volatile变量,杜绝指令重排。
参考文章
【转】线程安全的单例模式 (opens new window)
设计模式--单例模式(二)双重校验锁模式 - ·卿欢· - 博客园 (cnblogs.com) (opens new window)