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