前言:

在本文中会使用代码进行展示懒汉单例模式为什么需要进行二次判空;代码中使用到 CountDownLatch 倒计时器,不清楚CountDownLatch 使用的请参考此文“倒计时器:CountDownLatch”

代码展示:

1、懒汉式单例模式类
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
public class Singleton {

// 使用volatile禁止指令重排序
private static volatile Singleton sin = null;

public static int i = 0;// 标识有几个线程获取到了锁
public static int j = 0;// 标识系统中到底生成了几个实例

// 将构造器的修饰符设置为"private",可以防止在外部进行new实例对象
private Singleton() {
};

// 获取实例对象的方法,公共的方法。
public static Singleton getInstance() {
// 第一次判空。
if (sin == null) {
// 加锁
synchronized (Singleton.class) {
i++;
// 第二次判空。
if (sin == null) {
sin = new Singleton();
j++;
}
}
}
return sin;
}
}
2、多线程并发调用单例模式的测试类
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
public class ThreadTest implements Runnable  {

//实例化一个倒计树器,初始倒计数为10
static final CountDownLatch latch = new CountDownLatch(10);
static final ThreadTest demo = new ThreadTest();

@Override
public void run() {
try {
//实例对象生成
Singleton.getInstance();
//输出当前线程的名称
System.out.println(Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
finally {
//计数器进行减一
latch.countDown();
}
}

public static void main(String[] args) throws InterruptedException {
//创建一个长度为10的定长线程池
ExecutorService exec = Executors.newFixedThreadPool(10);
for (int i=0; i<10; i++){
//启动线程
exec.submit(demo);
}

//等待检查,阻塞main主线程,只有当CountDownLatch倒计数器为0时才会唤醒阻塞的main主线程
latch.await();

// 开启的10个线程中几个线程获取到了锁
System.out.println("共有 ( " + Singleton.i + " ) 个线程获取到对象锁");
// 最终生成了几个Singleton实例
System.out.println("最终生成了( " + Singleton.j + " )个Singleton实例对象");

// 关闭线程池
exec.shutdown();
}
}

运行上面的mian方法,会得到以下的一种输出结果(存在多种输出结果)

1
2
3
4
5
6
7
8
9
10
11
12
pool-1-thread-1
pool-1-thread-7
pool-1-thread-5
pool-1-thread-3
pool-1-thread-6
pool-1-thread-2
pool-1-thread-4
pool-1-thread-9
pool-1-thread-8
pool-1-thread-10
共有 ( 2 ) 个线程获取到对象锁
最终生成了( 1 )个Singleton实例对象

总结:

从运行结果可以看出,如果不进行第二次判空的话,那么在竟锁池(锁池)中如果还有活跃的线程在等待获取的锁的话,在锁释放后就会再次竞争获取锁,获取的锁的线程进入”就绪状态”,当cpu分配其”时间片”后进行线程的调度,从而线程进入”运行中状态”,并会去执行同步的代码块,如果在没加如二次判空的话,就会导致系统中存在多个实例,而在进行判空后,即使你获取到了锁,但在执行同步代码块时也会直接跳过。

竟锁池(锁池)的概念:Java中的锁池和等待池

不要忘记留下你学习的足迹 [点赞 + 收藏 + 评论]嘿嘿ヾ

一切看文章不点赞都是“耍流氓”,嘿嘿ヾ(◍°∇°◍)ノ゙!开个玩笑,动一动你的小手,点赞就完事了,你每个人出一份力量(点赞 + 评论)就会让更多的学习者加入进来!非常感谢! ̄ω ̄=