一、问题背景
单例,大家都应该清楚,面试时也经常被问到,大家也都会写。但就是这个最常规的东西,让我有了新的认识。
问题是这样的,我正准备做一个不同情况时的性能测试。Bean的生成方式就是单例,getInstance()。测试时需要测试并发下代码的正常执行,所以采用多线程方式来调用getInstance()。这时问题来了,发现getInstance()并非真正的单例,被初始化了多次,次数不固定。Why?
二、问题分析
经过研究发现问题是由于高并发导致的,有很大几率导致多个if(instance==null)的判断同时为true,引发重复初始化。
有问题的代码MySingleton(X)
01 |
package org.noahx.singleton; |
02 |
03 |
import java.util.Date; |
04 |
import java.util.concurrent.ExecutorService; |
05 |
import java.util.concurrent.Executors; |
06 |
import java.util.concurrent.TimeUnit; |
07 |
import java.util.concurrent.atomic.AtomicInteger; |
08 |
09 |
/** |
10 |
* Created with IntelliJ IDEA. |
11 |
* User: noah |
12 |
* Date: 4/28/13 |
13 |
* Time: 9:37 PM |
14 |
* To change this template use File | Settings | File Templates. |
15 |
*/ |
16 |
public class MySingleton { |
17 |
18 |
private static MySingleton mySingleton; |
19 |
20 |
/** |
21 |
* 原子计数器 |
22 |
*/ |
23 |
private static AtomicInteger count= new AtomicInteger( 0 ); |
24 |
25 |
private MySingleton() { |
26 |
27 |
//模拟长时间初始化 |
28 |
try { |
29 |
Thread.sleep( 5 ); |
30 |
} catch (InterruptedException e) { |
31 |
e.printStackTrace(); |
32 |
} |
33 |
System.out.println(count.incrementAndGet()); |
34 |
} |
35 |
36 |
public static MySingleton getInstance() { |
37 |
if (mySingleton == null ) { |
38 |
mySingleton = new MySingleton(); |
39 |
} |
40 |
return mySingleton; |
41 |
} |
42 |
43 |
public static void main(String[] args) { |
44 |
ExecutorService executorService = Executors.newCachedThreadPool(); |
45 |
for ( int c = 0 ; c < 20 ; c++) { |
46 |
executorService.execute( new TestRun()); |
47 |
} |
48 |
49 |
executorService.shutdown(); |
50 |
try { |
51 |
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); |
52 |
} catch (Exception e) { |
53 |
e.printStackTrace(); |
54 |
} |
55 |
56 |
} |
57 |
58 |
public static class TestRun implements Runnable { |
59 |
60 |
@Override |
61 |
public void run() { |
62 |
System.out.println(MySingleton.getInstance()); |
63 |
} |
64 |
} |
65 |
66 |
} |
01 |
1 |
02 |
org.noahx.singleton.MySingleton @35ab28fe |
03 |
2 |
04 |
org.noahx.singleton.MySingleton @86e293a |
05 |
3 |
06 |
org.noahx.singleton.MySingleton @7854a328 |
07 |
4 |
08 |
org.noahx.singleton.MySingleton @7ca3d4cf |
09 |
5 |
10 |
org.noahx.singleton.MySingleton @67e8a1f6 |
11 |
6 |
12 |
org.noahx.singleton.MySingleton @59e152c5 |
13 |
7 |
14 |
org.noahx.singleton.MySingleton @5801319c |
15 |
8 |
16 |
org.noahx.singleton.MySingleton @366025e7 |
17 |
org.noahx.singleton.MySingleton @366025e7 |
18 |
9 |
19 |
org.noahx.singleton.MySingleton @6037fb1e |
20 |
org.noahx.singleton.MySingleton @6037fb1e |
21 |
org.noahx.singleton.MySingleton @6037fb1e |
22 |
org.noahx.singleton.MySingleton @6037fb1e |
23 |
10 |
24 |
org.noahx.singleton.MySingleton @7b479feb |
25 |
11 |
26 |
org.noahx.singleton.MySingleton @375212bc |
27 |
12 |
28 |
org.noahx.singleton.MySingleton @6d4c1103 |
29 |
13 |
30 |
org.noahx.singleton.MySingleton @1cf11404 |
31 |
14 |
32 |
org.noahx.singleton.MySingleton @17592174 |
33 |
org.noahx.singleton.MySingleton @17592174 |
34 |
org.noahx.singleton.MySingleton @17592174 |
这里可以清楚的看到,被初始化的14次(每次运行不固定),而且类也有时是不同的实例。
所以这样的单例方式其实不是绝对意义上的单例。
三、问题修正
加锁吧,但又不想因为锁而影响运行的效率。所以采用了ReentrantLock(http://my.oschina.net/noahxiao/blog/101558),并使用双次==null判断解决性能问题。主要对getInstance()的代码进行了修改。(详见注释)
修正后的代码MySafetySingleton(V)
01 |
package org.noahx.singleton; |
02 |
03 |
import java.util.concurrent.ExecutorService; |
04 |
import java.util.concurrent.Executors; |
05 |
import java.util.concurrent.TimeUnit; |
06 |
import java.util.concurrent.atomic.AtomicInteger; |
07 |
import java.util.concurrent.locks.ReentrantLock; |
08 |
09 |
/** |
10 |
* Created with IntelliJ IDEA. |
11 |
* User: noah |
12 |
* Date: 4/28/13 |
13 |
* Time: 9:37 PM |
14 |
* To change this template use File | Settings | File Templates. |
15 |
*/ |
16 |
public class MySafetySingleton { |
17 |
18 |
private static MySafetySingleton mySingleton; |
19 |
/** |
20 |
* 原子计数器 |
21 |
*/ |
22 |
private static AtomicInteger count = new AtomicInteger( 0 ); |
23 |
/** |
24 |
* 锁 |
25 |
*/ |
26 |
private static ReentrantLock lock = new ReentrantLock(); |
27 |
28 |
private MySafetySingleton() { |
29 |
30 |
//模拟长时间初始化 |
31 |
try { |
32 |
Thread.sleep( 5 ); |
33 |
} catch (InterruptedException e) { |
34 |
e.printStackTrace(); |
35 |
} |
36 |
System.out.println(count.incrementAndGet()); |
37 |
} |
38 |
39 |
public static MySafetySingleton getInstance() { |
40 |
if (mySingleton == null ) { //为了不影响以后运行的速度(非第一次)首先判定是否为null |
41 |
try { |
42 |
lock.lock(); //先上锁,来保证下面这个代码不会同时被执行 |
43 |
if (mySingleton == null ) { //第二次判断是否为null,这样可以放弃由于初始并发而导致多次实例的问题 |
44 |
mySingleton = new MySafetySingleton(); |
45 |
} |
46 |
} finally { |
47 |
lock.unlock(); |
48 |
} |
49 |
} |
50 |
return mySingleton; |
51 |
} |
52 |
53 |
public static void main(String[] args) { |
54 |
ExecutorService executorService = Executors.newCachedThreadPool(); |
55 |
for ( int c = 0 ; c < 20 ; c++) { |
56 |
executorService.execute( new TestRun()); |
57 |
} |
58 |
59 |
executorService.shutdown(); |
60 |
try { |
61 |
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); |
62 |
} catch (Exception e) { |
63 |
e.printStackTrace(); |
64 |
} |
65 |
66 |
67 |
} |
68 |
69 |
public static class TestRun implements Runnable { |
70 |
71 |
@Override |
72 |
public void run() { |
73 |
System.out.println(MySafetySingleton.getInstance()); |
74 |
} |
75 |
} |
76 |
77 |
} |
01 |
1 |
02 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
03 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
04 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
05 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
06 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
07 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
08 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
09 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
10 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
11 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
12 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
13 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
14 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
15 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
16 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
17 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
18 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
19 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
20 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
21 |
org.noahx.singleton.MySafetySingleton @6cf84386 |
四、总结
看似简单的问题总是隐藏杀机,稍有不慎就会出现问题。