在一些程序设计中,希望对象只有一个实例,这时候就可以使用单例模式。单例模式的实现,在语法上 用一个私有的构造方法来保护类不能在外部被 new 出来,然后提供一个静态方法返回唯一的实例即可。
应用场景,例如:系统配置,整个系统有一个配置对象即可,如果有配置修改,通知这个唯一的对象就好了,每次读取配置只需从这个唯一的对象中获取。
下面是一些常见的写法,以及优缺点:
实现方式一
package cn.devdoc.dp.creational.singleton;/** *
* 最简单的单例模式,在多线程的情况下依然能保持单例。 *
* * @author CK * */public class Singleton1 { private final static Singleton1 instance = new Singleton1(); static { // 在这里初始化 instance 其实都一样,都是在类初始化即实例化instance。 } private Singleton1() { } public static Singleton1 getInstance() { return instance; }}这是最简单的单例模式,在多线程的情况下依然能保持单例。这种方式基于classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,没有达到 lazy loading 的效果。
实现方式二
package cn.devdoc.dp.creational.singleton;/** * * @author CK * */public class Singleton2 { private static Singleton2 instance; private Singleton2() { } public static Singleton2 getInstance() { if (instance == null) { // 在多线程的时候这里会出问题,导致的后果就是创建了多个实例 // 为测试效果,假设这里需要5秒才能完成创建实例 try { Thread.sleep(1000 * 5); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Singleton2(); } return instance; }}
在调用 getInstance 方法时才实例化对象,但多线程环境下会创建出多个对象。
实现方式三
package cn.devdoc.dp.creational.singleton;/** *
* 这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。 *
* * @author CK * */public class Singleton3 { private static Singleton3 instance = null; private Singleton3() { } public static synchronized Singleton3 getInstance() { if (instance == null) { // 为测试效果,假设这里需要5秒才能完成创建实例 try { Thread.sleep(1000 * 5); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Singleton3(); } return instance; }}这种写法能够在多线程中很好的工作,而且看起来它也具备很好的 lazy loading,但是,遗憾的是,效率很低,除了第一次,其它情况都不需要同步。
实现方式四
package cn.devdoc.dp.creational.singleton;/** *
* 双检锁,解决同步锁的性能问题,只有还没实例化的时候才会锁 *
* * @author CK * */public class Singleton4 { private volatile static Singleton4 instance; private Singleton4() { } public static Singleton4 getInstance() { if (instance == null) { // 只有instance还没被实例化的时候才会到这里,但有可能多个线程都执行到这了。 synchronized (Singleton4.class) { // 进入到if中的线程有多个,前面的线程可能已经实例化了instance,所以需要再次判断。 if (instance == null) { instance = new Singleton4(); } } } return instance; }}双检锁,解决同步锁的性能问题,只有还没实例化的时候才会锁
实现方式五
package cn.devdoc.dp.creational.singleton;/** *
* 基于静态内部类实现的单例模式 *
* * @author CK * */public class Singleton5 { private static class SingletonHolder { private static final Singleton5 INSTANCE = new Singleton5(); } private Singleton5() { } public static Singleton5 getInstance() { return SingletonHolder.INSTANCE; }}通过内部类实现的,其它还有通过枚举实现的。
测试
package cn.devdoc.dp.creational.singleton;import org.junit.Assert;import org.junit.Test;public class SingletonTest { @Test public void test0() { SingletonTest dp1 = new SingletonTest(); SingletonTest dp2 = new SingletonTest(); Assert.assertFalse(dp1.equals(dp2)); Assert.assertFalse(dp1.hashCode() == dp2.hashCode()); } @Test public void test1() { Singleton1 ins1 = Singleton1.getInstance(); Singleton1 ins2 = Singleton1.getInstance(); Assert.assertTrue(ins1.equals(ins2)); Assert.assertTrue(ins1.hashCode() == ins2.hashCode()); System.out.println(ins1); System.out.println(ins1.hashCode()); // 模拟多线程的场景 Runnable r = new Runnable() { @Override public void run() { Singleton1 single = Singleton1.getInstance(); System.out.println(single.hashCode()); } }; for (int i = 0; i <10; i++) { new Thread(r).start(); } } @Test public void test2() { // 模拟多线程的场景,多运行几次会发现有不一样的hashCode Runnable r = new Runnable() { @Override public void run() { Singleton2 single = Singleton2.getInstance(); System.out.println(single.hashCode()); } }; for (int i = 0; i <10; i++) { new Thread(r).start(); } } @Test public void test3() { // 模拟多线程的场景,无论怎么运行hashCode都是一样的 Runnable r = new Runnable() { @Override public void run() { Singleton3 single = Singleton3.getInstance(); System.out.println(single.hashCode()); } }; for (int i = 0; i <10; i++) { new Thread(r).start(); } } @Test public void test4() { // 模拟多线程的场景,无论怎么运行hashCode都是一样的 Runnable r = new Runnable() { @Override public void run() { Singleton4 single = Singleton4.getInstance(); System.out.println(single.hashCode()); } }; for (int i = 0; i <10; i++) { new Thread(r).start(); } } @Test public void test5() { Runnable r = new Runnable() { @Override public void run() { Singleton5 single = Singleton5.getInstance(); System.out.println(single.hashCode()); } }; for (int i = 0; i <10; i++) { new Thread(r).start(); } }}
测试结果
- test0:测试出普通类 new 出来的2个对象 地址和 hashCode 都不一样
- test1:模拟线程测试,hashCode 是一样的
- test2:模拟线程测试,会有 hashCode 不一样的情况,这是多线程造成的。
- test3:模拟线程测试,无论怎么测试,hashCode 都一样
- test4:模拟线程测试,无论怎么测试,hashCode 都一样
- test5:模拟线程测试,无论怎么测试,hashCode 都一样