zoukankan      html  css  js  c++  java
  • 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
    }
    

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

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

    一颗安安静静的小韭菜。文中如果有什么错误,欢迎指出。
  • 相关阅读:
    zzulioj--1716--毒(模拟水题)
    zzulioj--1715--土豪银行(贪心)
    35.Java中mian方法详解
    34.Java内存布局以及java各种存储区【详解】
    33.Java中static关键字
    32.java的this关键字
    31.Java构造方法
    30.Java对象的特证之一-封装
    29.Java匿名对象
    28.Java局部变量和成员变量
  • 原文地址:https://www.cnblogs.com/c-Ajing/p/13448331.html
Copyright © 2011-2022 走看看