Loading

设计模式-单例模式

 

时间:2023/01/04

 

一. 单例模式介绍

作用:所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。

实现方式(八种):

1. 饿汉式(静态常量)

2. 饿汉式(静态代码块)

3. 懒汉式(线程不安全)

4. 懒汉式(线程安全,同步方法)

5. 懒汉式(线程不安全,同步代码块)

6. 双重检查

7. 静态内部类

8. 枚举

 

二. 饿汉式(静态常量)

步骤:

1. 构造器私有化

2. 类的内部创建对象

3. 向外暴露一个静态的公共方法

代码:

package singleton;

public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton{
    // 1. 构造器私有化,外部不能new
    private Singleton(){

    }

    // 2. 本类内部创建对象实例
    private final static Singleton instance = new Singleton();

    // 3. 提供一个公有的静态方法,返回对象实例
    public static Singleton getInstance(){
        return instance;
    }
}

优缺点说明:

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

2. 缺点:在类加载的时候就完成实例化,没有达到Lazy Loading(懒加载)的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

3. 这种基于类加载的机制避免了多线程同步问题。但是在类加载时就进行实例化会存在很多问题,在单例模式中是通过调用getInstance方法来实现类加载,这种导致类加载的方式是我们希望看到的,因为我们调用getInstance方法就说明我们会用到这个单例对象,但是在实际中导致类加载的原因会有很多,如果不是通过调用getInstance方法导致类加载,就会出现我们不想使用单例对象,但是该对象却已经存在内存的情况,这会浪费内存空间,没有达到Lazy Loading的效果。

4. 结论:这种单例模式可用,但是可能造成内存浪费。

 

三. 饿汉式(静态代码块)

步骤:

1. 构造器私有化

2. 声明一个该类的对象

3. 在静态代码块中创建单例对象

4. 提供一个公有的静态方法,返回对象实例

package singleton;

public class SingletonTest02 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton02{
    // 1. 构造器私有化,外部不能new
    private Singleton02(){

    }

    // 2. 声明一个该类的对象
    private static Singleton02 instance;

    // 3. 在静态代码块中创建单例对象
    static {
        instance = new Singleton02();
    }

    // 4. 提供一个公有的静态方法,返回对象实例
    public static Singleton02 getInstance(){
        return instance;
    }
}

优缺点说明:

1. 与静态常量方法类似,优缺点和上面一样。

2. 结论: 这种单例模式可用,但是可能造成内存浪费。

 

四. 懒汉式(线程不安全)

步骤:

1. 声明一个该类的静态私有对象

2. 构造器私有化

3. 提供一个静态的公有方法,当使用到该方法时,采取创建该类的对象

package singleton.type3;

public class SingletonTest03 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton{

    private static Singleton instance;

    private Singleton(){}

    // 提供一个静态的公有方法,当使用到该方法时,才去创建instance(懒汉式)
    public static Singleton getInstance(){
        if(instance == null)
            instance = new Singleton();
        return instance;
    }
}

优缺点说明:

1. 起到了Lazy Loading的效果,但是只能在单线程下使用。

2. 如果在多线程下,一个线程进入了if(singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式(因为会违反单例模式的初衷)。

3. 结论:在实际开发中,不要使用这种方式。

 

五. 懒汉式(线程安全,同步方法)

步骤:

1. 声明一个该类的静态私有对象

2. 构造器私有化

3. 提供一个静态的公有方法,加入同步处理代码,解决线程安全问题

package singleton.type4;

public class SingletonTest04 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton{

    private static Singleton instance;

    private Singleton(){}

    // 提供一个静态的公有方法,加入同步处理的代码(synchronized),解决线程安全问题
    public static synchronized Singleton getInstance(){
        if(instance == null)
            instance = new Singleton();
        return instance;
    }
}

优缺点说明:

1. 解决了线程不安全问题。

2. 效率太低了,每个线程在想获取类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低。(每次调用getInstance()方法都需要同步,实际上真正实例化的只有一次)

3. 结论:在实际开发中,不推荐使用这种方式。

 

六. 懒汉式(线程不安全,同步代码块)

1. 声明一个该类的静态私有对象

2. 构造器私有化

3. 提供一个静态的公有方法,加入同步处理块,但是无法解决线程安全问题

package singleton.type5;

public class SingletonTest05 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton{

    private static Singleton instance;

    private Singleton(){}

    // 提供一个静态的公有方法
    public static Singleton getInstance(){
        // 同步代码块
        if(instance == null){
            synchronized (Singleton.class){
                instance = new Singleton();
            }
        }

        return instance;
    }
}

优缺点说明:

1. 这种方式,本意是想对第四种实现方式的改进,因为前面同步方式效率太低,改为同步产生实例化的代码块。

2. 但是这种同步并不能起到线程同步的作用。加入一个线程进入了if(singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

3. 结论:在实际开发中,不能使用这种方式。

 

七. 双重检查

volatile关键字:一种轻量级的同步机制,保证可见性,不保证原子性,并且禁止指令重排。

具体可见参考:https://blog.csdn.net/u012723673/article/details/80682208

代码如下:

package singleton.type6;

public class SingletonTest06 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton{

    // volatile: 一种轻量级的同步机制,保证可见性,不保证原子性,并且禁止指令重排。
    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;
    }
}

双重检查的思想:假设有三个线程a、b、c,如果线程a和b已经通过了第一次检查,但是都还没有进入同步代码块,而线程c还没有进入第一次检查,此时如果线程a先进入同步代码块,则线程b不能进入,然后a通过第二次检查并创建对象,之后a会退出同步代码块,此时b就可以进入到同步代码块中,但是由于instance对象已经被创建,b就不会创建新的对象,保证了内存中只有一个对象,然后b退出同步代码块。对于线程c,它此时执行第一次检查,由于instance不为null,所以不会执行到同步代码块,这样会大大提高效率,解决了懒汉式(同步方法)存在的问题。

优缺点说明:

1. Double-Check(双重检查)概念是多线程开发中常使用到的,如上述代码中所示,我们进行了两次if(singleton == null)检查,这样就可以保证线程安全。

2. 这样,实例化代码只用执行一次,后面再次访问时,判断if(singleton == null), 直接return实例化对象,也避免了反复进行方法同步。

3. 线程安全,延迟加载,效率较高。

4. 结论:在实际开发中,推荐使用这种单例设计模式。

 

八. 静态内部类

代码如下:

package singleton.type7;

public class SingletonTest07 {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);  // true
    }
}

class Singleton{

    // 1. 构造器私有化
    private Singleton(){}

    // 2. 写一个静态内部类,该类中有一个静态属性instance
    private static class SingletonInstance{
        private static final Singleton instnace = new Singleton();
    }

    // 3. 提供一个静态公有方法
    public static Singleton getInstance(){
        return SingletonInstance.instnace;
    }

}

静态内部类方法利用了静态内部类的两个特点:

1. 当外部类被加载(装载)时,静态内部类不会被加载,这实现了Lazy Loading。

2. 当调用到了getInstance()方法时,静态内部类会被加载,该类只会被加载一次,而且类加载是线程安全的,所以不会有安全问题。

优缺点说明:

1. 这种方式采用了类加载的机制来保证初始化实例时只有一个线程。

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

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

4. 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。

5. 结论:推荐使用。

 

九. 枚举

代码如下:

package singleton.type8;

public class SingletonTest08 {

    public static void main(String[] args) {
        Singleton instance = Singleton.instance;
        Singleton instance1 = Singleton.instance;

        System.out.println(instance == instance1);  // true

        instance.sayOK();   // ok~
    }
}

enum Singleton{
    instance;   // 属性

    public void sayOK(){
        System.out.println("ok~");
    }
}

优缺点说明:

1. 借助JDK 1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

2. 这种方式是Effective Java作者Josh Bloch提倡的方式。

3. 结论:推荐使用。

 

十. 单例模式注意事项和细节说明

注意事项和细节说明:

1. 单例模式保证了系统内存中只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。

2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new。

3. 单例模式使用的场景:需要频繁的进行创建和销毁对象、创建对象耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)。

 

 
posted @ 2023-01-05 12:27    阅读(14)  评论(0编辑  收藏  举报