单例模式

单例模式

单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象,一个最好的办法就是,让类自身负责保存它的唯一实例,这个类可以保证没有其他实例可以被创建。并且它可以提供一个访问该实例的方法。

单例模式使用场景:

需要频繁的进行创建和销毁对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到对象、工具类对象、频繁访问数据库或文件的对象(比如数据源,session工厂)。

单例模式的五种方式:

1、饿汉式

静态常量
1
2
3
4
5
6
7
8
9
10
11
class SingLeton1{
//将构造器私有化,防止用new来创建对象
private SingLeton1(){}
//我们声明一个静态属性SingLeton1
private static SingLeton1 instance = new SingLeton1();

//提供一个静态方法,可以返回SingLeton1的唯一对象
public static SingLeton1 getInstance(){
return instance;
}
}

测试:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
//无法直接new
//SingLeton1 singLeton1 = new SingLeton1();
SingLeton1 singLeton1 = SingLeton1.getInstance();
SingLeton1 singLeton2 = SingLeton1.getInstance();

System.out.println(singLeton1.hashCode() == singLeton2.hashCode()); //true

}
静态代码块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SingLeton2{

//将构造器私有化,防止用new来创建对象
private SingLeton2(){}

private static SingLeton2 insance;

//在静态代码块执行时,常见单例对象
static {
insance = new SingLeton2();
}

//提供一个静态方法,可以返回SingLeton2的唯一对象
public static SingLeton2 getInstance(){
return insance;
}

}

测试

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
//无法直接new
//SingLeton2 singLeton1 = new SingLeton2();
SingLeton2 singLeton1 = SingLeton2.getInstance();
SingLeton2 singLeton2 = SingLeton2.getInstance();

System.out.println(singLeton1.hashCode() == singLeton2.hashCode()); //true


}

静态代码块方法和静态常量方法其实类似,只不过静态代码块方法将类实例化的过程放在了静态代码块中,也是在类加载的时候,就执行静态代码块中的代码。

分析:

饿汉式的方式在初始化时就创建好了对象,不管你之后是否用到,都会创建以一个实例。

因此它没有线程安全的问题,但是浪费内存空间

2、懒汉式

线程不安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Singleton1{

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

private static Singleton1 singLeton1;

//当调用getInstance时,才创建单例对象
public static Singleton1 getInstance(){
if(singLeton1 == null){
singLeton1 = new Singleton1();
}

return singLeton1;
}

}

测试

1
2
3
4
5
6
public static void main(String[] args) {
Singleton1 singleton1 = Singleton1.getInstance();
Singleton1 singleton2 = Singleton1.getInstance();

System.out.println(singleton1.hashCode() == singleton2.hashCode()); //true
}

只能在单线程下使用,

如果在多线程下,一个线程进入了if(singleton == null) 判断语句块中,还未来得及去创建实例对象,另一个线程也通过了这个判断语句,这是便会产生多个实例。所以在多线程环境下不可使用这种方式。

线程安全,同步方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton2{
private Singleton2(){}

private static Singleton2 singleton2;

public static synchronized Singleton2 getInstance(){
if(singleton2 == null){
singleton2 = new Singleton2();
}

return singleton2;
}

}

测试

1
2
3
4
5
6
public static void main(String[] args) {
Singleton2 singleton1 = Singleton2.getInstance();
Singleton2 singleton2 = Singleton2.getInstance();

System.out.println(singleton1.hashCode() == singleton2.hashCode()); //true
}

这种方式解决了线程不安全问题,但效率太低了,每个线程在想获取类的实例时,都要进行同步。

3、双重检查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Singleton{
//构造方式私有化
private Singleton(){}
private static volatile Singleton singleton;

//加volatile就可以实现线程安全的延迟初始化,因为被volatile关键字修饰的变量是被禁止重排序的。
public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}

return singleton;
}
}

测试:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();

System.out.println(instance == instance1); //true


}

即保证了线程安全,有比直接上锁提高了执行效率,还节省了内存空间

4、静态内部类
1
2
3
4
5
6
7
8
9
10
11
12
class Singleton{
private Singleton(){}

private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}

}

测试

1
2
3
4
5
6
7
8
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();


System.out.println(singleton1.hashCode() == singleton2.hashCode()); //true

}

这种方式采用了类加载的机制来保证初始化时只有一个线程。

静态内部类方式在Singleton类被加载时并不会立即实例化,而是在需要实例化时,调用getInstance()方法,才会加载SingletonInstance类,从而完成Singleton的实例化。

5、枚举
1
2
3
enum Singleton{
INSTANCE
}

测试

1
2
3
4
5
6
7
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;

System.out.println(instance1.hashCode() == instance2.hashCode()); //true

}

枚举能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。


单例模式
https://johnjoyjzw.github.io/2020/10/05/单例模式/
Author
John Joy
Posted on
October 5, 2020
Licensed under