阅读35 返回首页    go 阿里云 go 技术社区[云栖]


爱上多线程——重复初始化问题

目录:[ - ]

一、问题背景 二、问题分析 三、问题修正 四、总结

一、问题背景

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

问题是这样的,我正准备做一个不同情况时的性能测试。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 }
有问题的输出结果 (X)
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(https://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 }
修正后的输出结果(V)
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

四、总结

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

最后更新:2017-04-03 19:06:48

  上一篇:go 10款好用的.NET图表控件推荐
  下一篇:go 51单片机程序参考大全