java设计模式创建篇------单例模式

设计核心

一个类只有一个对象实例,且由其自行创建,并提供一个访问它的全局访问点。

七种写法

饿汉模式

1
2
3
4
5
6
7
8
9
10
11
12
public class A{
//类在加载时就完成了初始化
private static A instance = new A();

private A(){
}

//可以迅速的获得实例对象
public static A getInstance{
return instance;
}
}

这一写法是在类的加载时完成的初始化,避免了多线程难以同步的问题,但是无论这一实例需不需要使用都对其进行了装载,使用的内存多,但是效率高。
顺带一提,其名字很有意思,饿汉模式中的饿汉体现在获取实例的迅速,因为饥饿所以要迅速的得到补充,所以一开始就把实例准备好。

懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class A{
//并不着急于初始化
private static A instance;

private A(){
}

//获取实例
public static A getInstance{
//首先判断是否已经初始化
if(instance == null){
//如果没有完成初始化才进行初始化
instance =new A();
}
return instance;
}
}

在类的加载时并不着急去初始化,而是等着需要调用的时候,根据需要进行初始化实例,但是效率不会太高,毕竟临时初始化需要时间,而且会有线程的同步问题,在多线程中,多个线程同时对其进行调用,在判断是否已经初始化过的时候,很难进行同步,产生多个实例。但是如果没有对其的调用的话,就不需要进行初始化,节约了内存空间。
这个名字也很贴切,懒汉不急,什么时候需要使用时,它才慢悠悠的进行准备,如果一直没有使用需求的话,它就一直这么懒下去。

懒汉模式(保证线程安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class A{
//并不着急于初始化
private static A instance;

private A(){
}

//获取实例并且对这个方法加锁
public static synchronized A getInstance{
//首先判断是否已经初始化
if(instance == null){
//如果没有完成初始化才进行初始化
instance =new A();
}
return instance;
}
}

这个写法与上一个的写法没有什么不同,唯一一点就是在获取实例的方法处加了一个锁用于同步,每次在使用这个函数之前先检查有没有别的线程在使用该函数,如果别的线程在使用则该函数,则会对这一函数加锁,使其等待其访问结束后才能调用,保证了多个线程之间对实例初始化的同步。但是这一机制导致每次调用实例时都需要对其进行检查,有很大的同步开销,所以不建议这一写法。

双重检查模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class A{
//使用了volatile关键字保证不同线程对这一变量的修改对其他线程可以立即可见。
private volatile static A instance;
private A(){
}
pubilc static A getInstance(){
//第一重检查
if(instance == null){
synchronzied(A.class){
//第二重检查
if(instance == null){
//如果没有初始化则进行初始化
instance = new A();
}
}
}
return instance;
}
}

在获取实例时对实例进行了两次的检查确认实例没有初始化后进行实例操作,其中第一次的检查是防止不必要的同步操作。这一方式保证了实例在第一次获取时进行实例化,效率很高,但是实例化需要时间,对于高并发操作还有一定影响,但是已经解决了同步问题和多余的资源消耗,并且只是第一次实例化时存在延迟,还是存在一定问题。

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
public class A{
private A(){
}
//由静态内部类获取实例
public static A getInstance(){
return AInClass.instance;
}
//静态内部类用来存放唯一的实例
private static class AInClass{
private static final A instance = new A();
}
}

在加载A类时不需要加载AInClass,只有第一次调用A的实例时,AInClass才会被加载,同时实例化。这种方法保证了线程同步性和单例的唯一性,同时不需要使用时不加载AInClass,合理的使用内存,同时也保证了效率,推荐使用。

枚举模式

1
2
3
4
5
6
public enum A {  
instance;//唯一实例
//逻辑方法
public void method() {
}
}

枚举模式十分简单只需要使用枚举类型即可,解决了多线程同步的问题,而且系统中只保存了这一个实例,节省了内存空间,但是枚举对象是jdk1.5之后添加的,操作上可能会有理解上的问题。

使用容器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class InstanceMap{
//使用Map容器来存储单例
private static Map<String,Object> map = new HashMap<String,Object>();
private InstanceMap(){
}
//实例化对象并将其存入容器中,以键值对的形式存取
public static void registerObject(String key,Object object){
//如果实例没有在之前存入则将其存入容器
if(!map.containsKey(key)){
map.put(key,object);
}
}
//
public static Object getInstance(String key){
//以键值对的形式对单例进行存取
return map.get(key);
}
}

这种方式充分的使用了HashMap的特点,同一个键对应一个实例对象,保证了单一实例,同时可以管理多种不同类型的单例,并且使用统一的类进行管理,条理清晰。

小结

单例模式的几种写法各自有对应的特点,我们可以根据自己项目的需求选择合适的使用方式,来控制实例化对象对资源的消耗。