[学习记录] 设计模式 2. 单例模式实现

单例模式实现

参考教程

1. 单例模式实现方式

  1. 饿汉式(静态变量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. (无效写法)懒汉式(同步代码块)
  6. 双重检查(推荐使用)
  7. 静态内部类(推荐使用)
  8. 枚举(最好的方式)

1.1 饿汉式(静态常量)

  1. 构造器私有化(防止 new
  2. 类内部创建对象
  3. 向外暴露一个静态的公共方法 getInstance()
public class SingletonType1 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2); // true
        System.out.println(instance1.hashCode() == instance2.hashCode()); // true
    }
}

class Singleton {
	// 类中创建对象实例
	private final static Singleton INSTANCE = new Singleton();
	
    // 外部不能实例化
    private Singleton() {
    }
    
    // 提供一个静态方法,返回实例化对象
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

优点:

  • 写法简单,类加载的时候就完成了实例化,避免线程同步问题。

缺点:

  • 类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。若从始至终都没用过该实例就会造成内存浪费

1.2 饿汉式(静态代码块)

class Singleton {
	// 静态代码块创建实例
    private static Singleton INSTANCE;
    
    static {
        INSTANCE = new Singleton();
    }
    
    // 外部不能实例化
    private Singleton() {
    }

    // 提供一个静态方法,返回实例化对象
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

总结:

  • 这种方法和上面 1.1 的方法类似,只不过将实例化的过程放在静态代码块,类加载的时候执行代码块中的代码,优缺点一样。

1.3 懒汉式(线程不安全)

class Singleton {
    private static Singleton INSTANCE;

    private Singleton() {
    }

    // 提供一个静态方法,返回实例化对象
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}

优点:

  • 实现 Lazy Loading 的效果,但是只能在单线程下使用。

缺点:

  • 多线程下,一个进程进入了 if (INSTANCE == null) 判断语句块,还没来得及往下执行,另一个线程也通过了这个判断语句,这会导致产生多个实例,所以说线程不安全。
  • 实际开发中不会使用这种方法。

1.4 懒汉式(线程安全,同步方法)

class Singleton {
    private static Singleton INSTANCE;

    private Singleton() {
    }

    // 提供一个静态方法,返回实例化对象, 加入同步处理代码, 解决线程不安全的问题
    public static synchronized Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}

优点:

  • 线程安全

缺点:

  • 效率太低,每个线程在获取类的实例的时候,执行 getInstance() 方法都要进行同步。而其实这个方法只需要执行一次实例化代码就够了,后面想获取该类实例,直接 return 就可以了。方法进行同步效率太低

1.5 (无效写法)懒汉式(同步代码块)

class Singleton {
    private static Singleton INSTANCE;

    private Singleton() {
    }

    // 提供一个静态方法,返回实例化对象, 加入同步处理代码块
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                INSTANCE = new Singleton();
            }
        }
        return INSTANCE;
    }
}

总结:

  • 多个线程已经进入了判断语句,synchronized 代码块没有意义,还是会产生多个实例。根本起不到线程同步的作用。
  • 实际开发不能用!!!

1.6 ★★★ 双重检查(推荐使用)

class Singleton {
    private static volatile Singleton INSTANCE;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE ==null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

INSTANCE = new Singleton(); 实例化对象可以分为三个步骤:

  1. 分配内存空间
  2. 初始化对象
  3. 将对象指向刚分配的内存空间

然而有些编译器为了性能的原因,可能将第 2 步和第 3 步进行重排序,顺序就成了:

  1. 分配内存空间
  2. 将对象指向刚分配的内存空间
  3. 初始化对象

如果不加 volatile 这样就可能出现这种情况:

Time 线程 A 线程 B
T1 检查到 INSTANCE 为空
T2 获取锁
T3 再次检查到 INSTANCE 为空
T4 INSTANCE 分配内存空间
T5 INSTANCE 指向内存空间
T6 检查到 INSTANCE 不为空
T7 访问 INSTANCE,(INSTANCE 此时未完成初始化)
T8 初始化 INSTANCE

在这种情况下,T7 时刻线程 B 对 INSTANCE 的访问,访问的是一个初始化未完成的对象。

所以需要在 INSTANCE 前加上关键字 volatile,使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。

总结:

  • 双检锁是多线程开发中经常使用到的操作。进行两次 if (INSTANCE == null) 检查,保证线程安全。
  • 实例化代码只执行一次,后面再次访问时,判断 if (INSTANCE == null) 直接 return 实例化对象,也避免了反复进行方法同步。
  • 线程安全,效率较高
  • 实际开发中也推荐使用!!!

1.7 ★★★ 静态内部类(推荐使用)

class Singleton {
    private Singleton() {
    }
    // 静态内部类
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

总结:

  • 外部类被装载,静态内部类不会加载,只有在调用 getInstance() 方法时才会加载静态内部类,且只会装载一次。
  • 解决饿汉式 Lazy Loading 问题。
  • 因为 JVM 在装载类的时候是线程安全的,所以该方法线程安全且效率较高。

1.8 ★★★★★ 枚举(最好的方式)

public class SingletonType8 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance1 == instance2); // true
        System.out.println(instance1.hashCode() == instance2.hashCode()); // true
        instance1.sayOk();
    }
}
/**
 * 枚举可以实现单例 (推荐使用)
 */
enum Singleton {
    //属性
    INSTANCE;
    // 方法
    public void sayOk() {
        System.out.println("ok");
    }
}

总结:

  • 《Effective Java》 书上说最佳的单例实现模式就是枚举模式
  • 借助 JDK 1.5 中添加的枚举类型来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
  • 推荐使用。

2. Runtime 类

饿汉式写法:

private static Runtime currentRuntime = new Runtime();

public static Runtime getRuntime() {
    return currentRuntime;
}

3. 单例模式注意事项

  1. 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  2. 当想实例化一个单例类的时候,必须要记住使用相对应的获取对象的方法,而不是使用 new

4. 单例模式使用场景

  • 需要频繁进行创建和销毁的对象
  • 创建对象时耗时过多或耗费资源过多,但又经常使用的对象
  • 工具类对象,频繁访问数据库或者文件的对象,如(数据源,session工厂等)

你可能感兴趣的:(设计模式,单例模式,学习,设计模式)