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

简介:

一、问题背景

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

问题是这样的,我正准备做一个不同情况时的性能测试。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(http://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

四、总结

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

目录
相关文章
|
2月前
|
存储 编译器 程序员
近4w字吐血整理!只要你认真看完【C++编程核心知识】分分钟吊打面试官(包含:内存、函数、引用、类与对象、文件操作)
近4w字吐血整理!只要你认真看完【C++编程核心知识】分分钟吊打面试官(包含:内存、函数、引用、类与对象、文件操作)
105 0
|
11月前
|
开发工具
游戏开发实战教程(5):重复执行和逻辑循环的区别
将循环分为重复执行和逻辑循环,应该是微信小游戏开发工具中所特有的。因为之前做游戏,无论是使用哪种工具或者哪种编程语言,对于循环来说,都只有一种,不会存在歧义或者误用。但是这里将循环分为了两种,如果误用的话会导致出现一些奇怪的问题。所以需要单独拿出来区分一下,避免掉进这个“坑”。
113 0
|
11月前
|
开发工具
微信小游戏开发实战5-重复执行和逻辑循环的区别
本篇主要内容包括了解帧的概念,以及理解重复执行和逻辑循环这两种循环积木块之间的区别。 如果你没有任何的游戏开发经验,欢迎阅读我的“人人都能做游戏”系列教程,它会手把手的教你做出自己的第一个小游戏。
67 0
|
12月前
|
设计模式 消息中间件 JavaScript
干掉 “重复代码”,这三种方式绝了!
干掉 “重复代码”,这三种方式绝了!
36906 2
干掉 “重复代码”,这三种方式绝了!
如何处理JDK线程池内线程执行异常?讲得这么通俗,别还搞不懂
本篇 《如何处理 JDK 线程池内线程执行异常》 这篇文章适合哪些小伙伴阅读呢? 适合工作中使用线程池却不知异常的处理流程,以及不知如何正确处理抛出的异常
|
存储 Java
还在为线程间上下文传递而烦恼,用TransmittableThreadLocal试试
还在为线程间上下文传递而烦恼,用TransmittableThreadLocal试试
373 0
多线程顺序运行的 4 种方法,面试随便问!
多线程顺序运行的 4 种方法,面试随便问!
196 0
|
算法 C++
C++ 基础复习系列2(打印图形类(循环)经典问题类)
C++ 基础复习系列2(打印图形类(循环)经典问题类)
C++ 基础复习系列2(打印图形类(循环)经典问题类)
重生之我在人间敲代码_Java并发基础_用“等待-通知”机制优化循环等待
在破坏占用且等待条件的时候,如果转出账本和转入账本不满足同时在文件架上这个条件,就用死循环的方式来循环等待。如果循环等待操作耗时非常短,而且并发冲突量也不大时,这个方案还挺不错的,因为这种场景下,循环上几次或者几十次就能一次性获取转出账户和转入账户了。但是如果循环等待操作耗时长,或者并发冲突量大的时候,循环等待这种方案就不适用了,因为在这种场景下,可能要循环上万次才能获取到锁,太消耗 CPU 了。