一、问题背景
单例,大家都应该清楚,面试时也经常被问到,大家也都会写。但就是这个最常规的东西,让我有了新的认识。
问题是这样的,我正准备做一个不同情况时的性能测试。Bean的生成方式就是单例,getInstance()。测试时需要测试并发下代码的正常执行,所以采用多线程方式来调用getInstance()。这时问题来了,发现getInstance()并非真正的单例,被初始化了多次,次数不固定。Why?
二、问题分析
经过研究发现问题是由于高并发导致的,有很大几率导致多个if(instance==null)的判断同时为true,引发重复初始化。
有问题的代码MySingleton(X)
01 | package org.noahx.singleton; |
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; |
10 | * Created with IntelliJ IDEA. |
14 | * To change this template use File | Settings | File Templates. |
16 | public class MySingleton { |
18 | private static MySingleton mySingleton; |
23 | private static AtomicInteger count= new AtomicInteger( 0 ); |
25 | private MySingleton() { |
30 | } catch (InterruptedException e) { |
33 | System.out.println(count.incrementAndGet()); |
36 | public static MySingleton getInstance() { |
37 | if (mySingleton == null ) { |
38 | mySingleton = new MySingleton(); |
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()); |
49 | executorService.shutdown(); |
51 | executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); |
52 | } catch (Exception e) { |
58 | public static class TestRun implements Runnable { |
62 | System.out.println(MySingleton.getInstance()); |
有问题的输出结果 (X)
02 | org.noahx.singleton.MySingleton @35ab28fe |
04 | org.noahx.singleton.MySingleton @86e293a |
06 | org.noahx.singleton.MySingleton @7854a328 |
08 | org.noahx.singleton.MySingleton @7ca3d4cf |
10 | org.noahx.singleton.MySingleton @67e8a1f6 |
12 | org.noahx.singleton.MySingleton @59e152c5 |
14 | org.noahx.singleton.MySingleton @5801319c |
16 | org.noahx.singleton.MySingleton @366025e7 |
17 | org.noahx.singleton.MySingleton @366025e7 |
19 | org.noahx.singleton.MySingleton @6037fb1e |
20 | org.noahx.singleton.MySingleton @6037fb1e |
21 | org.noahx.singleton.MySingleton @6037fb1e |
22 | org.noahx.singleton.MySingleton @6037fb1e |
24 | org.noahx.singleton.MySingleton @7b479feb |
26 | org.noahx.singleton.MySingleton @375212bc |
28 | org.noahx.singleton.MySingleton @6d4c1103 |
30 | org.noahx.singleton.MySingleton @1cf11404 |
32 | org.noahx.singleton.MySingleton @17592174 |
33 | org.noahx.singleton.MySingleton @17592174 |
34 | org.noahx.singleton.MySingleton @17592174 |
这里可以清楚的看到,被初始化的14次(每次运行不固定),而且类也有时是不同的实例。
所以这样的单例方式其实不是绝对意义上的单例。
三、问题修正(会有DCL问题,见三2)
加锁吧,但又不想因为锁而影响运行的效率。所以采用了ReentrantLock(http://my.oschina.net/noahxiao/blog/101558),并使用双次==null判断解决性能问题。主要对getInstance()的代码进行了修改。(详见注释)
修正后的代码MySafetySingleton(V?)
01 | package org.noahx.singleton; |
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; |
10 | * Created with IntelliJ IDEA. |
14 | * To change this template use File | Settings | File Templates. |
16 | public class MySafetySingleton { |
18 | private static MySafetySingleton mySingleton; |
22 | private static AtomicInteger count = new AtomicInteger( 0 ); |
26 | private static ReentrantLock lock = new ReentrantLock(); |
28 | private MySafetySingleton() { |
33 | } catch (InterruptedException e) { |
36 | System.out.println(count.incrementAndGet()); |
39 | public static MySafetySingleton getInstance() { |
40 | if (mySingleton == null ) { |
43 | if (mySingleton == null ) { |
44 | mySingleton = new MySafetySingleton(); |
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()); |
59 | executorService.shutdown(); |
61 | executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); |
62 | } catch (Exception e) { |
69 | public static class TestRun implements Runnable { |
73 | System.out.println(MySafetySingleton.getInstance()); |
修正后的输出结果(V?)
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 |
三2、问题修正(内部类)
首先谢谢David Wu提供的内部类方式的代码,经过测试确实方便的解决了问题。对于性能没有做比较,但使用时的性能应该不会有问题。具体见InnerClass与getInstance。
修正后代码MySafety2Singleton(V)
01 | package org.noahx.singleton; |
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; |
09 | * Created with IntelliJ IDEA. |
13 | * To change this template use File | Settings | File Templates. |
15 | public class MySafety2Singleton { |
17 | private static MySafety2Singleton mySafety2Singleton; |
21 | private static AtomicInteger count = new AtomicInteger( 0 ); |
23 | private MySafety2Singleton() { |
28 | } catch (InterruptedException e) { |
31 | System.out.println(count.incrementAndGet()); |
34 | public static MySafety2Singleton getInstance() { |
35 | if (mySafety2Singleton == null ) { |
36 | mySafety2Singleton = InnerClass.SINGLETON; |
38 | return mySafety2Singleton; |
41 | public static void main(String[] args) { |
42 | ExecutorService executorService = Executors.newCachedThreadPool(); |
43 | for ( int c = 0 ; c < 20 ; c++) { |
44 | executorService.execute( new TestRun()); |
47 | executorService.shutdown(); |
49 | executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); |
50 | } catch (Exception e) { |
56 | private static class InnerClass { |
57 | private static final MySafety2Singleton SINGLETON = new MySafety2Singleton(); |
60 | public static class TestRun implements Runnable { |
64 | System.out.println(MySafety2Singleton.getInstance()); |
修正后运行结果(V)
02 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
03 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
04 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
05 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
06 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
07 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
08 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
09 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
10 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
11 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
12 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
13 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
14 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
15 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
16 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
17 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
18 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
19 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
20 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
21 | org.noahx.singleton.MySafety2Singleton @1f194a4e |
运行正常而且也是懒加载。
四、总结
看似简单的问题总是隐藏杀机,稍有不慎就会出现问题。