java设计模式之单例模式(附多种实现方式)

一、单例模式介绍

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。简单说就是,整个程序有且仅有一个由该类提供的实例对象。当频繁创建对象需要消耗大量资源,或者在java应用中需要使用唯一全局实例变量时,都可以使用单例模式。

特点:

  1. 单例类只能有一个实例;
  2. 单例类持有自己类型的属性;
  3. 单例类对外提供获取该实例的静态方法。

主要优点:

  1. 避免频繁的创建销毁对象,可以提高性能;
  2. 在内存中只有一个对象,节省内存空间。

二、单例模式实现方式

  1. 饿汉式
public class Singleton {
    // 私有化构造函数,避免其他地方创建该实例
    private Singleton() {

    }

    private static final Singleton singleton = new Singleton();

    // 提供对外的静态访问方法
    public static Singleton getSingletonInstance() {
        return singleton;
    }

}

测试方法:

public class SingletonTest {

    public static void main(String[] args) {
        int count = 5;
        CountDownLatch latch = new CountDownLatch(count);
        for (int i = 0; i < count; i++) {
            new Thread(() -> {
                try {
                    latch.await();
                    System.out.println(Thread.currentThread().getName() + "-->" + Singleton.getSingletonInstance());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
            latch.countDown();
        }
    }

}

测试结果:

Thread-3-->learnbymaven.singleton.ehan.Singleton@464aa0d1
Thread-4-->learnbymaven.singleton.ehan.Singleton@464aa0d1
Thread-2-->learnbymaven.singleton.ehan.Singleton@464aa0d1
Thread-1-->learnbymaven.singleton.ehan.Singleton@464aa0d1
Thread-0-->learnbymaven.singleton.ehan.Singleton@464aa0d1

可以明显看到,程序中获取的实例是唯一的。

优点:实现方式简单、线程安全。
缺点:可能从始至终未使用过这个实例,造成内存的浪费。

  1. 懒汉式,线程不安全
public class Singleton {
    // 私有化构造函数,避免其他地方创建该实例
    private Singleton() {

    }

    private static Singleton singleton;

    // 提供对外的静态访问方法
    public static Singleton getSingletonInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

}

测试结果:

Thread-1-->learnbymaven.singleton.Singleton@35c2bd27
Thread-0-->learnbymaven.singleton.Singleton@45e76c2a
Thread-2-->learnbymaven.singleton.Singleton@45e76c2a
Thread-4-->learnbymaven.singleton.Singleton@45e76c2a
Thread-3-->learnbymaven.singleton.Singleton@35c2bd27

可以看到该实例并不是唯一的。

优点:实现方式简单、延迟加载。
缺点:线程不安全,只能适用于单线程应用。

  1. 懒汉式,线程安全
public class Singleton {
    // 私有化构造函数,避免其他地方创建该实例
    private Singleton() {

    }

    private static Singleton singleton;

    // 提供对外的静态访问方法
    public static synchronized Singleton getSingletonInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }

}

测试结果:

Thread-3-->learnbymaven.singleton.Singleton@77ddb68
Thread-4-->learnbymaven.singleton.Singleton@77ddb68
Thread-0-->learnbymaven.singleton.Singleton@77ddb68
Thread-1-->learnbymaven.singleton.Singleton@77ddb68
Thread-2-->learnbymaven.singleton.Singleton@77ddb68

此方法只是在懒汉式线程不安全的基础上加上了synchronized关键字,可以看到获取的实例是唯一的。

优点:实现方式简单、线程安全、延迟加载。
缺点:使用synchronized关键字加锁影响效率,并发性能低。

  1. 双检锁/双重校验锁

错误示范(不加volatile关键字):

public class Singleton {
    // 私有化构造函数,避免其他地方创建该实例
    private Singleton() {

    }

    // 没有加volatile关键字
    private static Singleton singleton;

    // 提供对外的静态访问方法
    public static Singleton getSingletonInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}

这段代码看起来没有问题,可能你放在多线程下测试也是可以通过的,但是其中却有一个致命隐患——指令重排序。

java中jvm初始化一个对象实际上可以分解成以下三个步骤:

  1. 分配内存空间
  2. 初始化对象
  3. 将对象指向刚分配的内存空间

但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:

  1. 分配内存空间
  2. 将对象指向刚分配的内存空间
  3. 初始化对象

由于这种情况,在多线程环境中,有的线程可能访问到初始化未完成的对象。

优点:延迟加载、效率高。
缺点:可能由于jvm编译器的指令重排序导致bug。

正确示范(使用volatile关键字):

public class Singleton {
    // 私有化构造函数,避免其他地方创建该实例
    private Singleton() {

    }

    // 没有加volatile关键字
    private static volatile Singleton singleton;

    // 提供对外的静态访问方法
    public static Singleton getSingletonInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}

优点:线程安全、延迟加载、性能高。
缺点:实现方式较复杂,可能忘记使用volatile关键字而留下隐患。

  1. 静态内部类
public class Singleton {
    // 私有化构造函数,避免其他地方创建该实例
    private Singleton() {

    }

    private static class SingletonHolder {
        private static final Singleton singleton = new Singleton();
    }

    // 提供对外的静态访问方法
    public static Singleton getSingletonInstance() {

        return SingletonHolder.singleton;
    }

}

这种方式也是可以延迟加载的,由jvm类加载机制可知,只有通过显式调用 getSingletonInstance方法时,才会显式装载 SingletonHolder 类,从而实例化 singleton。

优点:实现方式简单、线程安全、延迟加载。

  1. 枚举
public enum Singleton {
    MySingleton
}

优点:实现方式简单、线程安全、自动支持序列化机制。

不过实际工作中使用枚举类的方式实现单例模式比较不常见。

posted @ 2020-07-28 16:15  三分魔系  阅读(55)  评论(0编辑  收藏  举报