多线程中的单例 (优化版)

一、问题背景

单例,大家都应该清楚,面试时也经常被问到,大家也都会写。但就是这个最常规的东西,让我有了新的认识。

问题是这样的,我正准备做一个不同情况时的性能测试。Bean的生成方式就是单例,getInstance()。测试时需要测试并发下代码的正常执行,所以采用多线程方式来调用getInstance()。这时问题来了,发现getInstance()并非真正的单例,被初始化了多次,次数不固定。Why?

二、问题分析

经过研究发现问题是由于高并发导致的,有很大几率导致多个if(instance==null)的判断同时为true,引发重复初始化。

有问题的代码MySingleton(X)


01package org.noahx.singleton;
02  
03import java.util.Date;
04import java.util.concurrent.ExecutorService;
05import java.util.concurrent.Executors;
06import java.util.concurrent.TimeUnit;
07import 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 */
16public class MySingleton {
17  
18    privatestatic MySingleton mySingleton;
19  
20    /**
21     * 原子计数器
22     */
23    privatestatic AtomicInteger count=newAtomicInteger(0);
24  
25    privateMySingleton() {
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    publicstatic MySingleton getInstance() {
37        if(mySingleton == null) {
38            mySingleton =new MySingleton();
39        }
40        returnmySingleton;
41    }
42  
43    publicstatic void main(String[] args) {
44        ExecutorService executorService = Executors.newCachedThreadPool();
45        for(int c = 0; c < 20; c++) {
46            executorService.execute(newTestRun());
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    publicstatic class TestRun implements Runnable {
59  
60        @Override
61        publicvoid run() {
62            System.out.println(MySingleton.getInstance());
63        }
64    }
65  
66}
有问题的输出结果 (X)

011
02org.noahx.singleton.MySingleton@35ab28fe
032
04org.noahx.singleton.MySingleton@86e293a
053
06org.noahx.singleton.MySingleton@7854a328
074
08org.noahx.singleton.MySingleton@7ca3d4cf
095
10org.noahx.singleton.MySingleton@67e8a1f6
116
12org.noahx.singleton.MySingleton@59e152c5
137
14org.noahx.singleton.MySingleton@5801319c
158
16org.noahx.singleton.MySingleton@366025e7
17org.noahx.singleton.MySingleton@366025e7
189
19org.noahx.singleton.MySingleton@6037fb1e
20org.noahx.singleton.MySingleton@6037fb1e
21org.noahx.singleton.MySingleton@6037fb1e
22org.noahx.singleton.MySingleton@6037fb1e
2310
24org.noahx.singleton.MySingleton@7b479feb
2511
26org.noahx.singleton.MySingleton@375212bc
2712
28org.noahx.singleton.MySingleton@6d4c1103
2913
30org.noahx.singleton.MySingleton@1cf11404
3114
32org.noahx.singleton.MySingleton@17592174
33org.noahx.singleton.MySingleton@17592174
34org.noahx.singleton.MySingleton@17592174

这里可以清楚的看到,被初始化的14次(每次运行不固定),而且类也有时是不同的实例。

所以这样的单例方式其实不是绝对意义上的单例。

三、问题修正(会有DCL问题,见三2)

加锁吧,但又不想因为锁而影响运行的效率。所以采用了ReentrantLock(http://my.oschina.net/noahxiao/blog/101558),并使用双次==null判断解决性能问题。主要对getInstance()的代码进行了修改。(详见注释)

修正后的代码MySafetySingleton(V?)


01package org.noahx.singleton;
02  
03import java.util.concurrent.ExecutorService;
04import java.util.concurrent.Executors;
05import java.util.concurrent.TimeUnit;
06import java.util.concurrent.atomic.AtomicInteger;
07import 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 */
16public class MySafetySingleton {
17  
18    privatestatic MySafetySingleton mySingleton;
19    /**
20     * 原子计数器
21     */
22    privatestatic AtomicInteger count =new AtomicInteger(0);
23    /**
24     * 锁
25     */
26    privatestatic ReentrantLock lock =new ReentrantLock();
27  
28    privateMySafetySingleton() {
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    publicstatic 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        returnmySingleton;
51    }
52  
53    publicstatic void main(String[] args) {
54        ExecutorService executorService = Executors.newCachedThreadPool();
55        for(int c = 0; c < 20; c++) {
56            executorService.execute(newTestRun());
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    publicstatic class TestRun implements Runnable {
70  
71        @Override
72        publicvoid run() {
73            System.out.println(MySafetySingleton.getInstance());
74        }
75    }
76  
77}
修正后的输出结果(V?)

011
02org.noahx.singleton.MySafetySingleton@6cf84386
03org.noahx.singleton.MySafetySingleton@6cf84386
04org.noahx.singleton.MySafetySingleton@6cf84386
05org.noahx.singleton.MySafetySingleton@6cf84386
06org.noahx.singleton.MySafetySingleton@6cf84386
07org.noahx.singleton.MySafetySingleton@6cf84386
08org.noahx.singleton.MySafetySingleton@6cf84386
09org.noahx.singleton.MySafetySingleton@6cf84386
10org.noahx.singleton.MySafetySingleton@6cf84386
11org.noahx.singleton.MySafetySingleton@6cf84386
12org.noahx.singleton.MySafetySingleton@6cf84386
13org.noahx.singleton.MySafetySingleton@6cf84386
14org.noahx.singleton.MySafetySingleton@6cf84386
15org.noahx.singleton.MySafetySingleton@6cf84386
16org.noahx.singleton.MySafetySingleton@6cf84386
17org.noahx.singleton.MySafetySingleton@6cf84386
18org.noahx.singleton.MySafetySingleton@6cf84386
19org.noahx.singleton.MySafetySingleton@6cf84386
20org.noahx.singleton.MySafetySingleton@6cf84386
21org.noahx.singleton.MySafetySingleton@6cf84386

三2、问题修正(内部类)

首先谢谢David Wu提供的内部类方式的代码,经过测试确实方便的解决了问题。对于性能没有做比较,但使用时的性能应该不会有问题。具体见InnerClass与getInstance。

修正后代码MySafety2Singleton(V)


01package org.noahx.singleton;
02  
03import java.util.concurrent.ExecutorService;
04import java.util.concurrent.Executors;
05import java.util.concurrent.TimeUnit;
06import java.util.concurrent.atomic.AtomicInteger;
07  
08/**
09 * Created with IntelliJ IDEA.
10 * User: noah
11 * Date: 4/28/13
12 * Time: 9:37 PM
13 * To change this template use File | Settings | File Templates.
14 */
15public class MySafety2Singleton {
16  
17    privatestatic MySafety2Singleton mySafety2Singleton;
18    /**
19     * 原子计数器
20     */
21    privatestatic AtomicInteger count =new AtomicInteger(0);
22  
23    privateMySafety2Singleton() {
24  
25        //模拟长时间初始化
26        try{
27            Thread.sleep(5);
28        }catch (InterruptedException e) {
29            e.printStackTrace();
30        }
31        System.out.println(count.incrementAndGet());
32    }
33  
34    publicstatic MySafety2Singleton getInstance() {
35        if(mySafety2Singleton == null) {
36            mySafety2Singleton = InnerClass.SINGLETON;
37        }
38        returnmySafety2Singleton;
39    }
40  
41    publicstatic void main(String[] args) {
42        ExecutorService executorService = Executors.newCachedThreadPool();
43        for(int c = 0; c < 20; c++) {
44            executorService.execute(newTestRun());
45        }
46  
47        executorService.shutdown();
48        try{
49            executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
50        }catch (Exception e) {
51            e.printStackTrace();
52        }
53  
54    }
55  
56    privatestatic class InnerClass {
57        privatestatic final MySafety2Singleton SINGLETON = new MySafety2Singleton();
58    }
59  
60    publicstatic class TestRun implements Runnable {
61  
62        @Override
63        publicvoid run() {
64            System.out.println(MySafety2Singleton.getInstance());
65        }
66    }
67  
68}

修正后运行结果(V)


011
02org.noahx.singleton.MySafety2Singleton@1f194a4e
03org.noahx.singleton.MySafety2Singleton@1f194a4e
04org.noahx.singleton.MySafety2Singleton@1f194a4e
05org.noahx.singleton.MySafety2Singleton@1f194a4e
06org.noahx.singleton.MySafety2Singleton@1f194a4e
07org.noahx.singleton.MySafety2Singleton@1f194a4e
08org.noahx.singleton.MySafety2Singleton@1f194a4e
09org.noahx.singleton.MySafety2Singleton@1f194a4e
10org.noahx.singleton.MySafety2Singleton@1f194a4e
11org.noahx.singleton.MySafety2Singleton@1f194a4e
12org.noahx.singleton.MySafety2Singleton@1f194a4e
13org.noahx.singleton.MySafety2Singleton@1f194a4e
14org.noahx.singleton.MySafety2Singleton@1f194a4e
15org.noahx.singleton.MySafety2Singleton@1f194a4e
16org.noahx.singleton.MySafety2Singleton@1f194a4e
17org.noahx.singleton.MySafety2Singleton@1f194a4e
18org.noahx.singleton.MySafety2Singleton@1f194a4e
19org.noahx.singleton.MySafety2Singleton@1f194a4e
20org.noahx.singleton.MySafety2Singleton@1f194a4e
21org.noahx.singleton.MySafety2Singleton@1f194a4e
运行正常而且也是懒加载。


四、总结

看似简单的问题总是隐藏杀机,稍有不慎就会出现问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值