单例模式

来源:互联网 发布:数据库 schema 知乎 编辑:程序博客网 时间:2024/06/13 22:30

介绍


        单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。


        意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

        主要解决:一个全局使用的类频繁地创建与销毁。

        优点: 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。 

        缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。


例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
 * 单例模式
 
 * @author jerome_s@qq.com
 */
public class SingletonPatternDemo {
 
    public static void main(String[] args) {
 
        // 获取唯一可用的对象
        SingleObject object = SingleObject.getInstance();
 
        // 显示消息
        object.showMessage();
    }
}
 
class SingleObject {
 
    // 创建 SingleObject 的一个对象
    private static SingleObject instance = new SingleObject();
 
    // 让构造函数为 private,这样该类就不会被实例化
    private SingleObject() {
    }
 
    // 获取唯一可用的对象
    public static SingleObject getInstance() {
        return instance;
    }
 
    public void showMessage() {
        System.out.println("Hello World!");
    }
}
输出:Hello World!

单例模式的几种实现方式


1. 懒汉式(线程不安全


        是否 Lazy 初始化:

        是否多线程安全:

        实现难度:

        描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton {
    private static Singleton instance;
 
    private Singleton() {
    }
 
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}


2. 懒汉式(线程安全


        是否 Lazy 初始化:

        是否多线程安全:

        实现难度:

        描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
        优点:第一次调用才初始化,避免内存浪费。
        缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Singleton {
    private static Singleton instance;
 
    private Singleton() {
    }
 
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}


3. 饿汉式(推荐)


        是否 Lazy 初始化:

        是否多线程安全:

        实现难度:

        描述:这种方式比较常用,但容易产生垃圾对象。
        优点:没有加锁,执行效率会提高。
        缺点:类加载时就初始化,浪费内存。


1
2
3
4
5
6
7
8
9
10
class Singleton {
    private static Singleton instance = new Singleton();
 
    private Singleton() {
    }
 
    public static Singleton getInstance() {
        return instance;
    }
}

4. 双检锁/双重校验锁(DCL,即 double-checked locking) - “两个if”


        JDK 版本:JDK1.5 起

        是否 Lazy 初始化:

        是否多线程安全:

        实现难度:较复杂

        描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Singleton {
    private volatile static Singleton singleton;
 
    private Singleton() {
    }
 
    public static Singleton getSingleton() {
        if (singleton == null) {
            // 当调用的时候是不需要加锁的,只有在instance为null,
            // 并创建对象的时候才需要加锁,性能有一定的提升。
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

5. 登记式/静态内部类


        是否 Lazy 初始化:

        是否多线程安全:

        实现难度:一般

        描述:

        1.相应的基础知识

        什么是类级内部类?

        简单点说,类级内部类指的是有static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。

        类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。

        类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。

        类级内部类相当于其外部类的成员,只有在第一次被使用的时候才被会装载。


        多线程缺省同步锁的知识:

        大家都知道,在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地为您执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括: 

  1.由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时

  2.访问final字段时

  3.在创建线程之前创建对象时

  4.线程可以看见它将要处理的对象时


        只要不使用到这个类级内部类,那就不会创建对象实例,从而同时实现延迟加载和线程安全。这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

        当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.INSTANCE,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

        这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
 
    private Singleton() {
    }
 
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

6. 枚举


        JDK 版本:JDK1.5 起

        是否 Lazy 初始化:

        是否多线程安全:

        实现难度:

        描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
        

        这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,单元素的枚举类型已经成为实现Singleton的最佳方法。用枚举来实现单例非常简单,只需要编写一个包含单个元素的枚举类型即可。它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

1
2
3
4
5
public enum Singleton {
    INSTANCE;
    public void whateverMethod() {
    }
}

使用:Singleton.INSTANCE.whateverMethod(); // 保证是单例的


总结


         一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式

1 0