Java设计模式 - 单例模式

1. 定义

单例模式是Java中最简单的设计模式之一,属于创建型的设计模式。

2. 作用

单例模式可以保证一个类只有一个实例,为系统提供一个全局访问点。

3. 实现思路

  • 单例类的构造方法应定义为私有方法,防止在类外部通过构造方法实例化该类的对象;
  • 单例类的内部提供一个静态的方法供外部调用来获取单例对象。

4. 实现方法

单例模式的实现方法按创建单例的时机可分为两大类,每大类又可以细分为多种不同的实现。

  • 立即创建,即初始化单例类时就创建单例对象;
  • 延迟创建,等需要的时候再创建单例对象。

下面将对每种不同的实现方式做详细讲解。

4.1 立即创建

a. 饿汉式

最基础的单例实现方式,单例是static的,类加载时创建单例且只会创建一次,所以是线程安全的。

public class HungryManSingleton {

    private static HungryManSingleton instance = new HungryManSingleton();

    // 私有构造方法,防止外部实例化对象
    private HungryManSingleton() {}

    // 静态方法供外部代码调用获取单例对象
    public static HungryManSingleton getInstance() {
        return instance;
    }

    
    public static void main(String[] args) {
        // 获取单例的方式
        HungryManSingleton.getInstance();
    }

}

b. 枚举实现

据说是最好的实现方式,线程安全、简洁。

public enum EnumSingleton {

    INSTANCE;  // 1. 枚举类型为 public statis final T  可保证类初始化时创建单例且只创建一个

    // 2. 枚举有隐藏的private的构造方法,可保证外部不能初始化
    // EnumSingleton() {}

    // 3. 默认枚举实例的创建是线程安全的,保证了单例的线程安全性

    public void test() {
        System.out.println("this is a test.");
    }


    public static void main(String[] args) {
        // 获取单例方式
        EnumSingleton ins = EnumSingleton.INSTANCE;
        ins.test();
    }

}

《Effective Java》中称:

虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。

4.2 延迟创建

c. 懒汉式 - 基础实现

类加载时不创建单例,等需要时才手动创建。

public class LazyManSingleton {

    // 1.类加载时,不创建单例,单例置为null
    private static LazyManSingleton instance = null;

    // 2.私有构造函数禁止外部创建实例
    private LazyManSingleton() {}

    // 3.需要时才手动调用getInstance()创建单例
    public static LazyManSingleton getInstance() {
        if (instance == null) {
            instance = new LazyManSingleton();
        }
        return instance;
    }


    public static void main(String[] args) {
        // 获取单例方式
        LazyManSingleton.getInstance();
    }

}

这种实现方式在多线程情况下是线程不安全的,原因是第一个线程执行到创建对象new LazyManSingleton()这里时,因为创建对象需要一定时间,在第二个线程过来的时候对象可能还没创建完成,所以第二个线程判断对象为空也会去创建对象,最终导致两个线程分别获得了一个对象,单例模式失败。

d. 懒汉式改进 - 同步锁实现

给创建单例的方法getInstance()加同步锁synchronized,保证该方法同一时间只能被一个线程调用,可以避免单例重复创建。

public class LazyManSynSingleton {

    // 1.类加载时,不创建单例,单例置为null
    private static LazyManSynSingleton instance = null;

    // 2.私有构造函数禁止外部创建实例
    private LazyManSynSingleton() {}

    // 3.需要时才手动调用getInstance()创建单例,加同步锁保证只有一个线程能调用改方法
    public static synchronized LazyManSynSingleton getInstance() {
        if (instance == null) {
            instance = new LazyManSynSingleton();
        }
        return instance;
    }

    
    public static void main(String[] args) {
        // 获取单例方式
        LazyManSynSingleton.getInstance();
    }

}

同步锁的实现方法会造成一定的性能开销,实际上只在第一次调用的时候需要同步,没有必要每次调用getInstance()都进行线程同步,这样会造成额外的同步开销。

e. 懒汉式改进 - 双重校验锁

在加同步锁之前,先判断一下单例有没有创建,若单例已创建,则不需要加锁操作直接获取单例即可,可以提升性能。

public class LazyManDoubleCheckSingleton {

    // 1.类加载时,不创建单例,单例置为null
    private static LazyManDoubleCheckSingleton instance = null;

    // 2.私有构造函数禁止外部创建实例
    private LazyManDoubleCheckSingleton() {}

    // 3. 需要时才手动调用getInstance()创建单例,加入双重校验锁
    public static LazyManDoubleCheckSingleton getInstance() {
        // 校验锁1:第1个if判断单例是否已创建,若未创建才进入锁,若已创建直接返回单例
        if (instance == null) {
            synchronized (LazyManDoubleCheckSingleton.class) {
                // 校验锁2:第2个if判断单例是否已创建,若未创建就创建一个
                if (instance == null) {
                    instance = new LazyManDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }


    public static void main(String[] args) {
        // 获取单例方式
        LazyManDoubleCheckSingleton.getInstance();
    }

}

f. 静态内部类

在静态内部类里创建单例,装载静态内部类时才创建单例,静态内部类只会装载一次,所以只会创建一个单例。这种方式实现简洁、线程安全。

public class StaticInnerClassSingleton {

    // 静态内部类
    private static class InnerClass {
        private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }

    private StaticInnerClassSingleton() {}

    
    public static StaticInnerClassSingleton getInstance() {
    	// 获取单例的方式
        return InnerClass.instance;
    }

}

你可能感兴趣的:(设计模式)