对项目代码进行扫描时,出现静态扫描严重问题,发现是因为多线程环境下没有正确建立单例所致使。html
本项目使用的
JDK 1.7+
。java
项目代码以下(修改了类名,但核心没变)多线程
static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { synchronized (mutexObj) { if (tmp == null) { tmp = new Singleton(); cache = tmp; } } } return tmp; } }
按照项目生成单例代码,使用以下测试类进行测试并发
public class Test { public static void main(String[] args) { for (int i = 0; i < 3; i++) { Thread thread = new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + " " + Singleton.getInstance().toString()); } }); thread.setName("Thread" + i); thread.start(); } } static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { synchronized (mutexObj) { if (tmp == null) { tmp = new Singleton(); cache = tmp; } } } return tmp; } } }
输出结果以下:测试
Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@304e94a4
Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@304e94a4
Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@304e94a4线程
从结果看,都生成了同一个实例,彷佛不存在问题,多线程环境下确实不太好重现问题,现改动代码以下:3d
static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { System.out.println(Thread.currentThread().getName() + " in outer if block"); synchronized (mutexObj) { System.out.println(Thread.currentThread().getName() + " in synchronized block"); if (tmp == null) { System.out.println(Thread.currentThread().getName() + " in inner if block"); tmp = new Singleton(); cache = tmp; } System.out.println(Thread.currentThread().getName() + " out inner if block"); } System.out.println(Thread.currentThread().getName() + " out synchronized block"); } System.out.println(Thread.currentThread().getName() + " out outer if block"); return cache; } }
上述代码中添加了Thread.sleep(1)
这条语句,其中,Thread.sleep(1)
进行休眠时,线程不会释放拥有的锁,而且打印了相关的语句,便于查看线程正运行在哪里的状态。code
再次测试,输出结果以下:htm
Thread2 in outer if block
Thread1 in outer if block
Thread0 in outer if block
Thread2 in synchronized block
Thread2 in inner if block
Thread2 out inner if block
Thread2 out synchronized block
Thread0 in synchronized block
Thread2 out outer if block
Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@60b07af1
Thread0 in inner if block
Thread0 out inner if block
Thread0 out synchronized block
Thread1 in synchronized block
Thread0 out outer if block
Thread1 in inner if block
Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@625795ce
Thread1 out inner if block
Thread1 out synchronized block
Thread1 out outer if block
Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@642c39d2blog
从结果看,生成了3个不一样的实例,而且每一个线程都执行了完整的流程,而且可知单例的建立存在问题。在分析缘由前简单了解下多线程模型,多线程模型以下:
每一个线程有本身独立的工做空间,线程间进行通讯是经过主内存完成的,想了解详细内容可参见以下连接:内存模型或深刻理解java内存模型。
知道每一个线程会有一份tmp拷贝后,配合打印输出,就不难分析出缘由。
按照《Effective Java》一书中建立单例的推荐,可以使用以下两种解决方法
须要配合
volatile
关键字使用,而且须要JDK
版本在1.5
以上,核心代码以下
static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { tmp = cache; synchronized (mutexObj) { if (tmp == null) { tmp = new Singleton(); cache = tmp; } } } return tmp; } }
进行以下测试(添加打印语句方便分析):
public class Test { public static void main(String[] args) { for (int i = 0; i < 3; i++) { Thread thread = new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + " " + Singleton.getInstance().toString()); } }); thread.setName("Thread" + i); thread.start(); } } static class Singleton { private static volatile Singleton cache = null; private static Object mutexObj = new Object(); private Singleton() { } public static Singleton getInstance() { Singleton tmp = cache; if (tmp == null) { System.out.println(Thread.currentThread().getName() + " in outer if block"); synchronized (mutexObj) { System.out.println(Thread.currentThread().getName() + " in synchronized block"); tmp = cache; if (tmp == null) { System.out.println(Thread.currentThread().getName() + " in inner if block"); tmp = new Singleton(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } cache = tmp; } System.out.println(Thread.currentThread().getName() + " out inner if block"); } System.out.println(Thread.currentThread().getName() + " out synchronized block"); } System.out.println(Thread.currentThread().getName() + " out outer if block"); return tmp; } } }
输出结果以下:
Thread0 in outer if block
Thread0 in synchronized block
Thread0 in inner if block
Thread2 in outer if block
Thread1 in outer if block
Thread0 out inner if block
Thread0 out synchronized block
Thread1 in synchronized block
Thread1 out inner if block
Thread1 out synchronized block
Thread1 out outer if block
Thread0 out outer if block
Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@13883d5f
Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@13883d5f
Thread2 in synchronized block
Thread2 out inner if block
Thread2 out synchronized block
Thread2 out outer if block
Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@13883d5f
从结果中和线程运行步骤能够看到三个线程并发的状况下,只生成了惟一实例。
无
JDK
版本限制,也不须要使用volatile
关键字便可完成单例模式,核心代码以下:
static class Singleton { private Singleton() { } private static class InstanceHolder { public static Singleton instance = new Singleton(); } public static Singleton getInstance() { return InstanceHolder.instance; } }
进行以下测试:
public class Test { public static void main(String[] args) { for (int i = 0; i < 3; i++) { Thread thread = new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + " " + Singleton.getInstance().toString()); } }); thread.setName("Thread" + i); thread.start(); } } static class Singleton { private Singleton() { } private static class InstanceHolder { public static Singleton instance = new Singleton(); } public static Singleton getInstance() { return InstanceHolder.instance; } } }
运行结果以下:
Thread2 com.hust.grid.leesf.mvnlearning.Test$Singleton@71f801f7
Thread1 com.hust.grid.leesf.mvnlearning.Test$Singleton@71f801f7
Thread0 com.hust.grid.leesf.mvnlearning.Test$Singleton@71f801f7
该模式可保证使用时才会初始化变量,达到延迟初始化目的。
单例模式在多线程环境下不太好编写,而且不容易重现异常,编写时须要谨慎,在项目中遇到问题也须要多总结和记录。