如何编写单例模式

2025-12-09 06:57:12

1、饿汉式写法的步骤如下:

1. 将构造函数私有化.

2. 在类的内部创建一个静态的单例对象引用,并在初始化时调用构造函数为其赋值(在定义时赋值或在静态代码块中赋值)

3. 向外暴露一个静态方法`getInstance()`,用于获取单例对象.

其中为单例对象引用的赋值可以在定义成员变量时执行,也可以在静态代码块中执行.两种写法本质上是一样的,都会在类初始化时进行赋值.

2、在定义类实例时为单例对象引用赋值

// 饿汉式单例模式写法1

class Singleton {

// 将单例对象设为类变量

   private final static Singleton instance = new Singleton();

   // 将构造函数私有化

   private Singleton() {}

   // 向外暴露一个公共静态方法,用于获取单例对象

   public static Singleton getInstance() {

       return instance;

   }

}

3、在静态代码块为单例对象引用赋值

    // 饿汉式单例模式写法2

    class Singleton {

    

    // 将单例对象设为类变量

        private final static Singleton instance;

    

        // 在静态代码块中为单例对象赋值

        static {

            instance = new Singleton();

        }

    

        // 将构造函数私有化

        private Singleton() {}

    

        // 向外暴露一个公共静态方法,用于获取单例对象

        public static Singleton getInstance() {

            return instance;

        }

    }

4、饿汉式单例模式的优劣分析

- 优点: 利用了类在初始化时线程安全的特性,自动保证了线程安全.

- 缺点: 在类初始化时就创建了实例对象,没有实现懒加载.可能造成内存浪费.

1、懒汉式单例模式

2、懒汉式单例模式能实现懒加载,即在调用类的getInstance()方法时才创建单例对象.但这样需要我们在getInstance()方法上加锁,手动保证线程安全.

3、// 懒汉式单例模式写法

class Singleton {

    

    // 将单例对象设为类变量

    private static volatile Singleton instance;

    // 将构造函数私有化

    private Singleton() { }

    // 获取单例对象的方法

    // 使用synchronized将判断与赋值操作合并为原子操作

    public static synchronized Singleton getInstance() {

        if (instance == null) {

            instance = new Singleton();

        }

        return instance;

    }

}

4、有一种错误的懒汉式写法,这种写法只将获取单例对象的代码放入同步代码块.这种做法的本意是希望在创建出单例对象后再去获取单例对象不会引发线程同步,然而事与愿违,这种做法是错误的,因为判断与创建对象并不是一个原子操作,仍然会有多个线程进入同步代码块并多次创建对象.

实际上,要想达到这种效果,可以使用双重检查式写法.

// 懒汉式单例模式错误写法

class Singleton {

    

    // 将单例对象设为类变量

    private static volatile Singleton instance;

    // 将构造函数私有化

    private Singleton() { }

    // 获取单例对象的方法

    public static Singleton getInstance() {

        if (instance == null) {

            // 本意是想让创建对象时才进行线程同步,保证线程安全

            // 但是实际情况是,可能有多个线程进入该同步代码块,多次创建对象

            synchronized(Singleton.class) {

            instance = new Singleton();

            }

        }

        return instance;

    }

}

1、我们既希望单例对象实现懒加载,又想避免同步代码块带来的性能损失,因此我们对单例模式的创建过程加以分析,根据不同阶段判断是否使用同步代码块:

- 在单例对象未被创建完成的阶段,我们需要同步代码块来保证判断与创建单例对象两步组成一个原子操作.

- 在单例对象被创建出来后,我们不需要进入同步代码块,直接返回单例对象引用.

// 双重检查式单例模式

class Singleton {

    

    // 将单例对象设为类变量

    private static volatile Singleton instance;

    // 将构造函数私有化

    private Singleton() { }

    // 获取单例对象的方法

    public static Singleton getInstance() {

        if (instance == null) {

            // 若单例对象不存在,需要使用同步代码块保证判断和赋值组成原子操作

            synchronized (Singleton.class) {

                if (instance == null) {

                    instance = new Singleton();

                }

            }

        }

        // 若单例对象存在,直接返回其引用

        return instance;

    }

}

2、双重检查式单例模式的优点(没有缺点)

双重检查方式既保证了线程安全,又避免了线程同步带来的性能损失,是多线程开发中常用的编程思想.

1、在饿汉式单例模式中,我们利用了类在初始化时线程安全的特性,通过JVM的类加载机制保证线程安全.但似乎这种方式存在的缺点是不能实现懒加载,因为我们无法确定单例类的初始化时机.

JVM对内部类的加载机制可以保证内部类在外部类被加载时不会立即初始化,而会在该内部类被主动使用时才会初始化.利用这个性质,我们将单例对象实例放在内部类中,并在内部类加载时赋值.这样既实现了懒加载,又能通过JVM的类加载机制保证线程安全.

2、// 静态内部类式单例模式

class Singleton {

    

    // 将单例对象设为类变量

    private static volatile Singleton instance;

    // 将构造函数私有化

    private Singleton() { }

    // 私有的静态内部类,在加载时实例化单例对象

    private static class SingletonInstance {

        private static final Singleton INSTANCE = new Singleton();

    }

    // 获取单例对象的方法,该方法从内部类中获取单例对象

    public static synchronized Singleton getInstance() {

        return SingletonInstance.INSTANCE;

    }

}

3、静态内部类式单例模式的优劣分析

巧妙使用内部类的加载机制,既实现了懒加载,又保证了类型安全.

1、上面四种单例模式的写法都存在一个致命问题:无法避免反射攻击,即通过反射机制更改构造方法的权限为`public`.

使用枚举式单例模式可以有效防止反射攻击,其原理见[为什么要用枚举实现单例模式](https://www.cnblogs.com/chiclee/p/9097772.html)

2、// 枚举式单例模式

enum Singleton {

    INSTANCE; // 单例对象属性

    // 枚举类同样可以拥有方法

    public void function() {

        System.out.println("this is a function");

    }

}

3、枚举类单例模式的优劣分析

枚举类单例模式可以从根本上防止反射攻击,其原理比较深奥,现阶段的我还不懂,等我学会了之后一定回来补上这部分内容.

声明:本网站引用、摘录或转载内容仅供网站访问者交流或参考,不代表本站立场,如存在版权或非法内容,请联系站长删除,联系邮箱:site.kefu@qq.com。
猜你喜欢