未完待续

1.常见的线程安全的单例模式有哪些?

思路:

(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。

(2)在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型。

(3)定义一个静态方法返回这个唯一对象。

饿汉式,懒汉式,双重检查,枚举

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//线程安全的饿汉式单例
public class Singleton {

// 将自身实例化对象设置为一个属性,并用static、final修饰
private static final Singleton instance = new Singleton();

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

// 静态方法返回该实例
public static Singleton getInstance() {
return instance;
}
}
//线程安全的“懒汉式”单例
public class Singleton {

// 将自身实例化对象设置为一个属性,并用static修饰
private static Singleton instance;

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

// 静态方法返回该实例,加synchronized关键字实现同步
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
//线程安全的“双重检查”单例(懒汉式)
public class DoubleCheckLock {
private static Instance instance;
public static Instance getInstance(){
// 第一次检查
if(instance==null){
// 第一次检查为null再进行加锁,降低同步带来的性能开销
synchronized (DoubleCheckLock.class){
// 第二次检查
if(instance==null){
instance=new Instance();
}
}
}
return instance;
}
}
//枚举单例
public enum Singleton {
INSTANCE;
public Singleton getInstance(){
return INSTANCE;
}
}

2.双重检查的单例模式为什么要进行两次判空检查?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DoubleCheckLock {
private static Instance instance;
public static Instance getInstance(){
// 第一次检查
if(instance==null){
// 第一次检查为null再进行加锁,降低同步带来的性能开销
synchronized (DoubleCheckLock.class){
// 第二次检查
if(instance==null){
instance=new Instance();
}
}
}
return instance;
}
}

第一次判断是为了验证是否创建对象

第二次判断是为了避免重复创建单例,因为可能会存在多个线程通过了第一次判断在等待锁,来创建新的实例对象。

3.双重检查的单例模式有问题吗?如果有?怎么解决?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DoubleCheckLock {
private static Instance instance;
public static Instance getInstance(){
// 第一次检查
if(instance==null){
// 第一次检查为null再进行加锁,降低同步带来的性能开销
synchronized (DoubleCheckLock.class){
// 第二次检查
if(instance==null){
// 问题出在此处
instance=new Instance();
}
}
}
return instance;
}
}

当第一次检查时,读取instance不为null时,instance引用的对象可能还没有完成初始化!原因在于多线程下的重排序。

instance=new Instance() new创建一个对象不是原子操作,分为三步:

1)分配对象的内存空间

2)初始化对象

3)设置instance引用指向刚分配的内存地址

但是在一些编译器上(如JIT),2)和3)可能会发生重排序。

Java规范保证了重排序不会改变单线程内的程序执行结果。但是在多线程下,若线程A在执行instance=new Instance();时发生了重排序,先执行了3),

这时候线程B刚好获取到了instance不为null,接着去访问对象。但是这个时候线程A还未执行2),即还没被线程A初始化,那么这个时候线程B得到的就是

一个还没有初始化的对象。

解决方案:

(1)不允许2)和3)重排序

(2)允许2)和3)重排序,但不允许其他线程“看到”这个重排序

解决:通过将instance声明为volatile型来禁止2)和3)之间的重排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class VolatileDoubleCheckLock {
// 将instance声明为volatile型
**private volatile static Instance instance;**
public static Instance getInstance(){
// 第一次检查
if(instance==null){
// 第一次检查为null再进行加锁,降低同步带来的性能开销
synchronized (VolatileDoubleCheckLock.class){
// 第二次检查
if(instance==null){
// 多线程下将禁止2)和3)之间的重排序
instance=new Instance();
}
}
}
return instance;
}
}

4.枚举单例如何保证线程安全?

https://www.cnblogs.com/z00377750/p/9177097.html