java 实现线程安全的单例模式

一、平时使用的软件中,例如 回收站、线程池、文件系统等,都只有一个实例,这些都是单例模式的典型应用。

  单例模式:确保某个类只有一个实例,并提供一个全局访问点来访问这个实例。

  单例模式有三个要点:

  1. 某个类只能有一个实例

  2. 必须自行创建这个实例

  3. 必须自行向整个系统提供这个实例。

  以上三个要点提示着我们的代码编写需要注意,构造函数必须私有,否则在其他类中便可以调用构造函数创建实例,难以保证实例的唯一性。

二、单例模式分为饿汉模式和懒汉模式

//饿汉模式:(线程安全)
public class Singleton1 {
//    静态私有成员变量
    private static Singleton1 instance = new Singleton1();
//    私有构造函数
    private Singleton1() {
    }
//    静态公有工厂方法,返回唯一实例
    public static Singleton1 getInstance() {
        return instance;
    }
}
// 懒汉模式:(线程不安全,需要通过双重检查锁定机制控制)
public class Singleton2 {
//    静态私有成员变量
    private static Singleton2 instance = null;
//    私有构造函数
    private Singleton2() {
    }
//    静态公有工厂方法,判断成员变量是否为空,不为空则实例化
    public static Singleton2 getInstance() {
        if(instance == null)
            instance = new Singleton2();
        return instance;
    }
}

  优缺点:

   饿汉模式不需要考虑线程安全问题,调用速度和访问速度优于懒汉模式,但是由于它不管是否被调用都会提前创建类的实例,所以资源利用效率比较低,系统加载时间比较长。

懒汉模式实现了延迟加载,但是需要克服多个线程同时访问的问题,需要通过双重检查锁定机制进行控制,导致系统性能受到一定影响。

三、下面两个方法实现懒汉模式的线程安全。

  为什么会线程不安全?

   假设有两个线程 A B,其中 A 执行到检查方法,即 if(instance == null) 前,实例都没有被创建,那么 A 会得到 ture 的结果,但是此时调度算法选择 B 线程运行,那么当 B 执行 到 if(instance == null) 时得到的也是 true,那这就很尴尬了,两个线程都会执行 instance = new Singleton2(); 从而创建了两个实例。

  1.双重检查锁定机制。

为了避免以上这种尴尬的情况,需要将这两行代码加上同步锁。但这还不够完美,每次调用函数得到实例都要试图加上一个同步锁,而加锁是一个非常耗时的操作,没有必要的情况下应该尽量避免。基于这种想法,我们可以在加锁前再次判断实例是否为空。这就是双重检查锁定机制。

public class Singleton3 {
//    私有静态成员变量
    private static Singleton3 instance = null;
//    私有构造函数
    private Singleton3() {
    }
//    共有静态工厂方法
    public static Singleton3 getInstance() {
//        判断 instance 是否为空,为空->加锁,创建实例(为了进程安全,再次判断),不为空->返回实例
        if(instance == null) {
            synchronized (Singleton3.class) {
                if(instance == null)
                    instance = new Singleton3();
            }
        }
        return instance;
    }
}

  2. 使用静态内部类创建实例 。JAVA 语言中最好的实现方法)

public class Singleton4 {
//    私有构造函数
    private Singleton4() {
    }
//    静态内部类
    private static class HolderClass{
        private static final Singleton4 instance = new Singleton4();
    }
//    静态公有工厂方法,返回内部类中创建的实例
    public static Singleton4 getInstance() {
        return HolderClass.instance;
    }
}

  当装载 Singleton4 类时,instance 不一定被初始化,因为它是内部类的成员变量。Singleton4 没有被主动使用,只有调用 getInstance 方法时,才会装载 Singleton4 类,从而实例化 instance

使用静态内部类很好得在实现了延迟加载的同时,保证初始化 instance 时只有一个线程。

相关推荐