HiSEN

关于 Java static

零、背景

同事分享《Effective Java》
其中第十章,并发部分例子有争议
变量是否需要(代码如下) static?
几个大佬说需要加,我众目睽睽下反驳不需要,略尴尬

// 是否需要加 static?才能保证单例正确
private volatile Singleton singleton;

一、程序关键代码

1.1 原程序(错误)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
private volatile Singleton singleton;

public Singleton() {
}

private Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

1.2 正确程序

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

public class Singleton {
private static volatile Singleton singleton;

public Singleton() {
}

private Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

二、验证

2.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
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
public class Singleton {
private static final AtomicInteger COUNT = new AtomicInteger(0);
// 通过volatile关键字来确保安全
private volatile Singleton singleton;

public Singleton() {
}

private Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
System.out.println("new:" + COUNT.addAndGet(1));
singleton = new Singleton();
}
}
}
return singleton;
}

@Test
public void test() {
int taskCount = 700;
// 锁住所有线程,等待并发执行
final CountDownLatch begin = new CountDownLatch(1);

final ExecutorService exec = Executors.newFixedThreadPool(taskCount);

for (int index = 0; index < taskCount; index++) {
submitTask(begin, exec);
}
System.out.println("开始执行");
// begin 减1 ,开始并发执行
begin.countDown();
//关闭执行
exec.shutdown();
}

private void submitTask(CountDownLatch begin, ExecutorService exec) {
Runnable run = () -> {
try {
// 等待,所有一起执行
begin.await();
//开始模拟等待。。。
Singleton singleton = new Singleton();
Singleton instance = singleton.getInstance();
System.out.println(Objects.isNull(instance));
} catch (InterruptedException e) {
e.printStackTrace();
}
};
exec.submit(run);
}
}

2.2 验证结果

带 static 的结果符合预期
对象只创建一次,并且返回结果均不为 null

2.2.1 不带 static

开始执行
new:1
new:2
new:3
false
true
省略几十行….

2.2.2 带 static

开始执行
new:1
false

三、原因分析

static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。
而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。

关键:static 变量在内存中只有一个副本,由所有对象共享。

四、参考