饿汉式创建
该方式在类加载时就会创建该单实例对象。
- 静态成员变量
java
package per.mjn.pattern.singleton.demo1;
/**
* 饿汉式:静态成员变量
*/
public class Singleton {
// 1. 私有构造方法
private Singleton() {}
// 2. 创建静态成员变量
private static Singleton instance = new Singleton();
// 3. 提供一个静态方法,返回静态成员变量
public static Singleton getInstance() {
return instance;
}
}
java
package per.mjn.pattern.singleton.demo1;
public class Client {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
}
}
测试输出
bash
true
- 静态代码块
java
package per.mjn.pattern.singleton.demo2;
/**
* 饿汉式:静态代码块
*/
public class Singleton {
// 1. 私有构造方法
private Singleton() {}
// 2. 声明Singleton类型的变量
private static Singleton instance; // null
// 3. 在静态代码块中创建Singleton类型的对象
static {
instance = new Singleton();
}
// 4. 提供一个静态方法,返回Singleton类型的对象
public static Singleton getInstance() {
return instance;
}
}
java
package per.mjn.pattern.singleton.demo2;
public class Client {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
测试输出
bash
true
- 枚举方式创建
枚举类实现单例模式借助枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯-一种不会被破坏的单例实现模式。
java
package per.mjn.pattern.singleton.demo6;
/**
* 枚举实现方式
*/
public enum Singleton {
INSTANCE;
}
java
package per.mjn.pattern.singleton.demo6;
public class Client {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance == instance2);
}
}
测试结果
bash
true
懒汉式创建
类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建。
java
package per.mjn.pattern.singleton.demo3;
/**
* 懒汉式
*/
public class Singleton {
// 私有构造方法
private Singleton() {}
// 声明Singleton类型的变量instance
private static Singleton instance;
// 获取唯一可用的对象
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
java
package per.mjn.pattern.singleton.demo3;
public class Client {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
}
}
测试输出
bash
true
双重检查锁方式
java
package per.mjn.pattern.singleton.demo4;
/**
* 双重检查锁方式
*/
public class Singleton {
// 私有构造方法
private Singleton() {}
// 声明Singleton类型的变量instance
private static Singleton instance;
// 获取唯一可用的对象
public static Singleton getInstance() {
// 第一次判断,如果instance的值不为null,不需要抢占锁,直接返回instance
if (instance == null) {
synchronized (Singleton.class) {
// 第二次判断,如果instance的值不为null,不需要抢占锁,直接返回instance
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
双重检查锁模式在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针异常的问题,只需要使用volatile 关键字,volatile 关键字可以保证可见性和有序性。
java
package per.mjn.pattern.singleton.demo4;
/**
* 双重检查锁方式
*/
public class Singleton {
// 私有构造方法
private Singleton() {}
// 声明Singleton类型的变量instance
private static volatile Singleton instance;
// 获取唯一可用的对象
public static Singleton getInstance() {
// 第一次判断,如果instance的值不为null,不需要抢占锁,直接返回instance
if (instance == null) {
synchronized (Singleton.class) {
// 第二次判断,如果instance的值不为null,不需要抢占锁,直接返回instance
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
添加volatile关键字之后的双重检査锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。
静态内部类方式
静态内部类单例模式中实例由内部类创建,由于在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。
java
package per.mjn.pattern.singleton.demo5;
/**
* 静态内部类方式
*/
public class Singleton {
// 私有构造方法
private Singleton() {}
// 定义一个静态内部类
private static class SingletonHolder {
// 在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
// 提供公共的访问方式
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
第一次加载singleton类时不会去初始化INSTANCE,只有第一次调用getInstance(),虚拟机加载singletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证 singleton 类的唯一性。
静态内部类单例模式是比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。