设计模式之单例模式

单例模式

意在保证一个类只有一个实例,并提供一个全局访问点.

通过将类的构造器私有化,只向外提供一个单独的创建唯一对象的方法来实现一个类只创建一个对象.主要有三种实现 1. 饿汉模式 2. 懒汉模式 3.使用枚举实现单例.下面通过具体实例来分析如何在多线程的情况下仍能满足单例模式创建对象.

饿汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private Singleton(){};
//1. 使用静态变量
// private static Singleton singleton = new Singleton();
//2.使用静态代码块 未使用时造成内存空间浪费
private static Singleton singleton;
static {
singleton = new Singleton();
}
public Singleton newInstance(){
return singleton;
}
}

这种方式不会影响到多线程的线程安全问题,因为类的装载机制在初始化对象的时候是保证不会有第二个线程进入的。但是有个很大的弊端是他不是lazy-loading的,这回产生资源的浪费,比如创建完对象后,自始至终没用过。所以不推荐使用

懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Singleton {
private void Singleton(){}
private static Singleton singleton;

// 普通的线程不安全的
public Singleton newInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}

//2.静态内部类 一方面能够达到lazy-loading的效果,另一方面能够保证线程安全,因为jvm保证初始化的时候别的线程是不能进入的
private static class newSingleton{
private static final Singleton INSTANCE = new Singleton();
}

public Singleton newInstanceInnerClass(){
return newSingleton.INSTANCE;
}
}

上面代码的第一种是不能保证线程安全的,多线程下会导致失效。而第二种使用静态内部类的方式实现能够实现线程安全得宜于类的加载机制,类似饿汉模式。但同时具有lazy-Loading的特性。是常用的单例模式用法。

double-check双重锁

双重锁结构只是为了满足多线程安全而建立的,是一种特殊的懒汉模式,思想同懒汉模式相同.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DoubleCheck {
private void DoubleCheck(){}
private volatile DoubleCheck doubleCheck; //使用volatile保证原子性,防止重排序
public DoubleCheck newInstance(){
if(doubleCheck == null){
// 当两个线程同时到这步 a先进同步块,在a进入后获得instance后,b获得锁,进入同步块,
// 这时候下一个判断就起到作用了,这时候的doubleCheck不为空,
// 直接return,否则又️新建了一个对象
synchronized (DoubleCheck.class){
if(doubleCheck == null){
doubleCheck = new DoubleCheck();
}
}
}
return doubleCheck;
}
}

实际上double-check也是懒汉模式的一种,能够保证线程安全。很完美。。

使用枚举创建单例

在stackoverflow中有一个问题What is an efficient way to implement a singleton pattern in Java?

这里的回答引用到了Effective Java 作者 Joshua Bloch对于单例的处理方式:使用枚举

1
2
3
4
5
6
7
8
public enum SingletonEnum {
INSTANCE;
private final String[] favoriteSongs =
{ "Hound Dog", "Heartbreak Hotel" };
public void printFavorites() {
System.out.println(Arrays.toString(favoriteSongs));
}
}

这种方法等同与使用public的构造方法,并且比上面的更加简洁,并且即使面对复杂的反射与序列化也能够保证单一的实例.虽然现实代码中应用的较少,但是这种使用枚举类型是创建单例的最佳方法.

1
2
3
4
5
public void testEnum() {
SingletonEnum singletonEnum = SingletonEnum.INSTANCE;
SingletonEnum singletonEnumCopy = SingletonEnum.INSTANCE;
Assert.assertEquals(singletonEnum, singletonEnumCopy); //通过
}
-------------本文结束感谢您的阅读-------------

本文标题:设计模式之单例模式

文章作者:NanYin

发布时间:2019年05月30日 - 12:05

最后更新:2019年08月12日 - 13:08

原始链接:https://nanyiniu.github.io/2019/05/30/2019-05-30-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。