Java单例模式

2023-12-13 15:43:47

单例模式介绍

单例模式的作用:

  • 节省内存和计算,实例只创建一次,不必重复创建
  • 保证结果正确,比如单例计数器,用作多线程的数据统计
  • 方便管理,例如日期工具类,字符串工具类,不需要创建那么多的实例

单例模式适用场景:

  • 无状态的工具类:比如日志工具类,不管是在哪里适用,我们需要的只是它帮我们记录日志信息,除此之外,并不需要在它的实例对象上存储任何状态,这时候我们就只需要一个实例对象即可。
  • 全局信息类:比如我们在一个类上记录网站的访问次数,我们不希望有的访问被记录在对象A上,有的却记录在对象B上,这时候我们就让这个类成为单例。

饿汉式——静态常量

// 饿汉式单例
class Singleton1 {

    // 指向自己实例的私有静态引用,主动创建
    private static Singleton1 singleton1 = new Singleton1();

    // 私有的构造方法
    private Singleton1(){}

    public static Singleton1 getSingleton1(){
        return singleton1;
    }
    // 以自己实例为返回值的静态的公有
}

优点:这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到lazy loading的效果,如果从始至终都未使用过这个实例,则会造成内存的浪费

懒汉式——线程不安全

// 懒汉式单例
public class Singleton2 {
 
    // 指向自己实例的私有静态引用
    private static Singleton2 singleton2;
 
    // 私有的构造方法
    private Singleton2(){}
 
    // 以自己实例为返回值的静态的公有方法,静态工厂方法
    public static Singleton2 getSingleton2(){
        // 被动创建,在真正需要使用时才去创建
        if (singleton2 == null) {
            singleton2 = new Singleton2();
        }
        return singleton2;
    }
}

我们从懒汉式单例可以看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用。

这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

懒汉式——线程安全,同步方法(不推荐用)

public class Singleton4 {
    private static Singleton4 instance;

    private Singleton4() {

    }

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

解决上面第三种方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。

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

双重锁校验(线程安全)

public class Singleton6 {
    private volatile static Singleton6 instance;

    private Singleton6() {

    }

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

优点:线程安全,延迟加载,效率较高

双重检查的好处:保证线程安全和性能

volatile作用:创建对象不是原子操作,要防止重排序

静态内部类(可用)

public class Singleton7 {
    private Singleton7() {
    }

    private static class SingletonInstance {

        private static final Singleton7 INSTANCE = new Singleton7();
    }

    public static Singleton7 getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

静态内部类在Singleton类被装载时不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。

类的静态属性只会在第一次加载类的时候初始化,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程无法进入。

优点:避免了线程不安全,延迟加载,效率高。

枚举(推荐用)

public enum  Singleton8 {
    INSTANCE;

    public void getInstance() {

    }
}

不仅能避免多线程同步问题,还是懒加载,而且还能防止反序列化重新创建新的对象。

各种写法的适用场合

  • 最好的方法是利用枚举,因为还可以防止反序列化重新创建新的对象
  • 非线程同步的方法不能使用
  • 如果程序一开始要加载的资源太多,那么就应该使用懒加载
  • 饿汉式如果是对象的创建需要配置文件就不适用
  • 懒加载虽然好,但是静态内部类这种方式会引入编程复杂性,大部分情况还是推荐枚举,简单。

文章来源:https://blog.csdn.net/qq_39940634/article/details/134971929
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。