单例模式概念

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
Java中的Runtime即是单例。

单例的实现方式

饿汉式

Runtime类也是使用的饿汉式。

//饿汉式
public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
         return instance;  
    }  
}

懒汉式(线程不安全)

//懒汉模式
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

证明不安全

这种方法会存在一个问题就是,在并发情况下无法保证单例。比如两个线程同时运行到 if (Instance == null) 的时候,这个时候因为对象并未实例化,所以都是得到true.这个时候就会创建两个对象了,不能保证单例。

public class Test {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(SingleTon.getInstance());
            }
        }, "线程1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(SingleTon.getInstance());
            }
        }, "线程2").start();

    }
}
//多运行几次得到
SingleTon@6d0ca4c7
SingleTon@455d5a7e

饿汉式 方法锁(线程安全、效率低)

因为每次执行都是同步的,所以效率比较低

public class SingleTon {
    private SingleTon(){};
    private static SingleTon Instance;

    public synchronized static SingleTon getInstance() {
        if (Instance == null) {
            Instance = new SingleTon();
        }
        return Instance;
    }
}

饿汉式 Double CheckLock 双重校验锁(线程安全、效率高)

public class SingleTon {
    private SingleTon(){};
    private volatile static SingleTon Instance;

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

需要注意 Instance 采⽤ volatile 关键字修饰也是很有必要。
Instance 采⽤ volatile 关键字修饰也是很有必要的, Instance = new Singleton();
这段代码其实是分为三步执⾏:
1. 分配内存空间
2. 初始化
3. 将Instance指向分配的内存地址(这个时候已经不为null了)

但是由于 JVM 具有指令重排的特性,执⾏顺序有可能变成 1→3→2。
指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。
例如,线程 T1 执⾏了 1 和 3,此时 T2 调⽤ getInstance() 后发现 Instance 不为空,因此返回Instance,
但此时 Instance 还未被初始化。使⽤ volatile 可以禁⽌ JVM 的指令重排,保证在多线程环境下也能正常运⾏。
总结:如果不使用volatile关键词修饰,可能会导致拿到的对象是未被初始化的。

饿汉式 静态内部类

通过JVM来保证线程安全,高效

public class SingleTon {
    private SingleTon() {
    }
    private static class SingleTonHolder {
        private static final SingleTon INSTANCE = new SingleTon();
    }

    public static SingleTon getInstances() {
        return SingleTonHolder.INSTANCE;
    }
}

枚举方式

public enum SingleTonEnum {
    /**
     * 实例
     */
    INSTANCE;

    public void doSomething() {
        //todo
    }
}

如何选用

  • 单例对象 占用资源少,不需要延时加载,枚举 好于 饿汉

  • 单例对象 占用资源多,需要延时加载,静态内部类 好于 懒汉式

评论

博客
分类
标签
归档
关于