在一些程序设计中,希望对象只有一个实例,这时候就可以使用单例模式。
单例模式的实现,在语法上 用一个私有的构造方法来保护类不能在外部被 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都一样