单例模式
单例设计模式是设计模式中使用最普遍的模式之一。用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例。
单例带来的好处
1.对于频繁创建的对象,可以省略创建对象所花费的时间,这对于一些重量级的对象而言,是非常可观的一笔开销。
2.由于new操作的次数减少,因而对于系统内存的使用频率也会降低,这也将减轻GC压力,缩短GC停顿时间。
单例的实现
单例模式的核心在于通过一个接口返回唯一对象的实例,最简单的例子:
public class SimpleSingleInstance {
private SimpleSingleInstance() {
System.out.println("SimpleSingleInstance");
}
private static SimpleSingleInstance instance = new SimpleSingleInstance();
public static SimpleSingleInstance getInstance() {
return instance;
}
}
这个例子实现方式非常简单,而且十分可靠。
1.一个private 访问级别的构造函数,确保单例不会再系统中被实例化
2.getInstance保证只能获取到一个唯一的单例对象
但是这个类有个很明显的缺点,没有做到延迟加载,也就是说在jvm加载单例类的时候,单例对象就会被创建。
public class SimpleSingleInstance {
private SimpleSingleInstance() {
System.out.println("SimpleSingleInstance");
}
private static SimpleSingleInstance instance = new SimpleSingleInstance();
public static SimpleSingleInstance getInstance() {
return instance;
}
}
public static void log(){
System.out.println("log");
}
当我们调用SimpleSingleInstance.log();的时候程序输出:
SimpleSingleInstance
log
虽然我们没有使用单例类,但是还是被创建出来了,如果这个单例创建比较耗时的话我们在调用他的静态方法的时候,也就造成了该方法第一次的执行时间加长,为了解决这种问题我们引入了赖加载。
public class LazySingleInstance {
private LazySingleInstance() {
System.out.println("LazySingleInstance");
} private static LazySingleInstance instance = null;
public synchronized static LazySingleInstance getInstance() {
if (instance == null) {
instance = new LazySingleInstance();
}
return instance;
}
}
首先将instance初始值为null,确保系统启动的时候不会加载,其次在getIntance判断instance是不是为null不为null采取创建对象,当然也加上了同步锁保障多线程同时访问不会创建出多个对象,也就违反了我们写单例的目的。
使用上面的例子虽然实现了延迟加载,但是也加入的同步锁,再多线程中,它的耗时也会大大增加。
for (int i = 0; i < 5; i++) {
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
long beginTime = System.currentTimeMillis();
SimpleSingleInstance.getInstance();
spendTime += (System.currentTimeMillis() - beginTime);
if (i == 100000 - 1) {
System.out.println("SimpleSingleInstance spend:" + (spendTime));
}
}
}
}.start();
}
我们开启五个线程去执行SimpleSingleInstance耗时
SimpleSingleInstance
SimpleSingleInstance spend:19
SimpleSingleInstance spend:17
SimpleSingleInstance spend:17
SimpleSingleInstance spend:19
SimpleSingleInstance spend:20
我们开启五个线程去执行SimpleSingleInstance耗时
LazySingleInstance
LazySingleInstance spend:268
LazySingleInstance spend:268
LazySingleInstance spend:273
LazySingleInstance spend:274
LazySingleInstance spend:274
开启五个线程同时执行,可以看出性能相差巨大。
为了解决这个问题,我们对LazySingleInstance进行改造
public class LazySingleInctanceV2 {
private LazySingleInctanceV2() {
System.out.println("LazySingleInctanceV2");
}
private volatile static LazySingleInctanceV2 instance = null;
public static LazySingleInctanceV2 getInstance() {//去掉这里同步锁,在大部分情况下instance都不为null
if (instance == null) {
synchronized (LazySingleInctanceV2.class) {
if (instance == null) {//双重判断避免构建多个对象
instance = new LazySingleInctanceV2();
}
}
}
return instance;
}
}
LazySingleInctanceV2
LazySingleInctanceV2 spend:28
LazySingleInctanceV2 spend:29
LazySingleInctanceV2 spend:29
LazySingleInctanceV2 spend:29
LazySingleInctanceV2 spend:29
可以看出运行时间基本上和SimpleSingleInstance保持在了一个数量级
当然还有一种更加简单的实现方式
public class StaticSingleInstance {
private StaticSingleInstance() {
System.out.println("StaticSingleInstance");
}
public static StaticSingleInstance getInstance() {
return SingleInstanceHolder.instance;
}
private static class SingleInstanceHolder {//内部类来维护单例的实例
private static StaticSingleInstance instance = new StaticSingleInstance();
}
}
在上面的实现中,使用内部类来维护单例的实例,当单例被加载的时候,其内部类并不会被初始化,当StaticSingleInstance类被加载入JVM的时候,不会初始化单例类,而当调用getInstance的时候才回去加载SingleInstanceHolder从而初始化instance。
StaticSingleInstance
StaticSingleInstance spend:22
StaticSingleInstance spend:23
StaticSingleInstance spend:23
StaticSingleInstance spend:23
StaticSingleInstance spend:24
网友评论