閱讀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單片機程序參考大全