单例设计模式实现(JAVA)

356 阅读5分钟

单例设计模式提供了多线程情况下保证实例唯一性的解决方案。

评估方式:线程安全、高性能、懒加载。

实现方式:饿汉模式、懒汉模式、Double-Check、Holder、枚举及以上各种模式的优化和扩展。

饿汉模式

1. 线程安全:在初始化的过程种会被收集进()方法里,该方法能百分百保证同步。

备注:

1. init is the (or one of the) constructor(s) for the instance, and non-static field initialization. 2. clinit are the static initialization blocks for the class, and static field initialization.

init是instance实例构造器,对非静态变量解析初始化

clinit是class类构造器对静态变量,静态代码块进行初始化

2. 性能:可能造成内存浪费,在被ClassLoader加载后,可能会很长一段时间才被使用,意味着instance会长时间存在于instance实例开辟的堆内存里,如果单例类的属性很多,会造成更大的浪费。getInstance没有加锁,多线程情况下性能比较高。

3. 无法进行懒加载。

/**
 * 饿汉模式:指全局的单例实例在第一次被使用时构建
 * 补充: final 不允许被继承
 */
public final class Singleton {
    // 实例变量
    private byte[] data = new byte[1024];

    // 定义实例时直接初始化
    private static Singleton instance = new Singleton();
    
    // 定义私有的构造函数 -> 不允许外部new
    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;   
    }
}

懒汉模式

1. 线程不安全: 没有锁或其他保障,多线程情况下,可能生成多个实例。

2. 性能:初始化很快。

3. 可以进行懒加载。

/**
 * 懒汉模式:指全局的单例实例在类装载时构建
 * 补充: final 不允许被继承
 */
public final class Singleton {
    // 实例变量
    private byte[] data = new byte[1024];

    // 定义实例但不初始化
    private static Singleton instance;
    
    // 定义私有的构造函数 -> 不允许外部new
    private Singleton() {
    }

    public static Singleton getInstance() {
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;   
    }
}

懒汉模式 + synchronized

1. 线程安全

2. 性能:初始化很快,但获取实例在多线程情况下性能不高,因为synchronized关键字会使方法同一时刻只能被一个线程访问。

3. 可以进行懒加载。

/**
 * 懒汉模式:指全局的单例实例在类装载时构建
 * 补充: final 不允许被继承
 */
public final class Singleton {
    // 实例变量
    private byte[] data = new byte[1024];

    // 定义实例但不初始化
    private static Singleton instance;
    
    // 定义私有的构造函数 -> 不允许外部new
    private Singleton() {
    }
    
    // 加上同步控制,每次只允许一个线程进入
    public static synchronized Singleton getInstance() {
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;   
    }
}

Double-Check + Volatile

1. 线程安全:首次初始化的时候加锁,之后允许多个线程进行getInstance方法的调用来获取实例。

2. 性能:性能高,只在实例没被初始化的时候,加锁一次(即在可能发生竞争的模块加锁)。

3. 可以进行懒加载。

/**
 * 补充: final 不允许被继承
 */
public final class Singleton {
    // 实例变量
    private byte[] data = new byte[1024];

    // 定义实例但不初始化
    // 如果instance先被实例化了,这两个未被实例化,会抛出空指针异常,volatile保证指令不被重排序
    private volatile static Singleton instance;

    // 
    Connection connection;
    Socket socket;
    
    // 定义私有的构造函数 -> 不允许外部new
    private Singleton() {
        this.connection;
        this.socket;
    }
    
    public static Singleton getInstance() {
        if (null == instance) {
            // instance为null时才创建,避免每次都进入同步代码块 
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;   
    }
}

Holder

1. 线程安全:类似饿汉模式,实例化时候,被收集到()方法里了,该方法时同步方法,可以保证内存可见性,JVM指令的顺序行和原子性。

2. 性能:性能较好,在初始化的时候并不加载,在实例化的时候才新建实例。这种方法时单例设计最好的设计之一,也是目前使用比较广的设计之一。

3. 可以进行懒加载。

/**
 * 补充: final 不允许被继承
 */
public final class Singleton {
    // 实例变量
    private byte[] data = new byte[1024];

    // 定义私有的构造函数 -> 不允许外部new
    private Singleton() {
    }
    
    // 在静态内部类持有Singleton实例,并且可被直接初始化
    private static class Holder {
        private static Singleton instance = new Singleton();
    }
    
    // 获取Holder的instance静态属性
    public static Singleton getInstance() {
        return Holder.instance;   
    }
}

枚举方式

1. 线程安全:枚举类型不允许被继承,且只能被实例化一次。

2. 性能:性能较好,在初始化的时候并不加载,在实例化的时候才新建实例。这种方法时单例设计最好的设计之一,也是目前使用比较广的设计之一。

3. 不能进行懒加载。

public enum Singleton {
    INSTANCE;
    
    // 实例变量
    private byte[] data = new byte[1024];

    Singleton() {
        // 观察什么时候被执行
        System.out.println("INSTANCE has be initialized.");
    }

    public static void method() {
        // 调用该方法会主动使用Singleton,INSTANCE将会被实例化
    }
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

枚举方式 + Holder

1. 线程安全:枚举类型不允许被继承,且只能被实例化一次。

2. 性能:性能较好,在初始化的时候并不加载,在实例化的时候才新建实例。这种方法时单例设计最好的设计之一,也是目前使用比较广的设计之一。

3. 可以进行懒加载。

public class Singleton {
    // 实例变量
    private byte[] data = new byte[1024];

    // 定义私有的构造函数 -> 不允许外部new
    private Singleton() {
    }

    private enum EnumHolder {
        INSTANCE;

        private Singleton instance;

        EnumHolder() {
            this.instance = new Singleton();
        }

        private Singleton getSingleton() {
            return instance;
        }
    }
    
    public static Singleton getInstance() {
        return EnumHolder.INSTANCE.getSingleton();    }
}

面试相关:

云从科技、美团

参考:

1. 《Java高并发编程详解》 汪文君

2. 单例模式 - 维基百科 zh.wikipedia.org/wiki/%E5%8D…

3. init和clinit的区别 - StackOverFlow stackoverflow.com/questions/8…