35
阿裏雲
技術社區[雲棲]
愛上多線程——重複初始化問題
一、問題背景
單例,大家都應該清楚,麵試時也經常被問到,大家也都會寫。但就是這個最常規的東西,讓我有了新的認識。
問題是這樣的,我正準備做一個不同情況時的性能測試。Bean的生成方式就是單例,getInstance()。測試時需要測試並發下代碼的正常執行,所以采用多線程方式來調用getInstance()。這時問題來了,發現getInstance()並非真正的單例,被初始化了多次,次數不固定。Why?
二、問題分析
經過研究發現問題是由於高並發導致的,有很大幾率導致多個if(instance==null)的判斷同時為true,引發重複初始化。
有問題的代碼MySingleton(X)
01
|
package org.noahx.singleton;
|
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;
|
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次(每次運行不固定),而且類也有時是不同的實例。
所以這樣的單例方式其實不是絕對意義上的單例。
三、問題修正
加鎖吧,但又不想因為鎖而影響運行的效率。所以采用了ReentrantLock(https://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 )
{ //為了不影響以後運行的速度(非第一次)首先判定是否為null
|
42
|
lock.lock(); //先上鎖,來保證下麵這個代碼不會同時被執行
|
43
|
if (mySingleton
== null )
{ //第二次判斷是否為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
|
四、總結
看似簡單的問題總是隱藏殺機,稍有不慎就會出現問題。
最後更新:2017-04-03 19:06:48