单例模式&多例模式

初识单例模式

    单例模式规定一个类仅有一个实例,并提供一个访问它的全局访问点。就是说单例类必须满足一下几点

  1. 单例类只有一个实例
  2. 单例类必须自己创建这个实例
  3. 必须向整个系统提供这个实例

    单例模式作为众多设计模式中最简单的设计模式之一,原理非常简单,下面主要介绍单例模式的几种不同的实现方式。

实现方式

饿汉式

public class HungarySingleton {

    private HungarySingleton(){};
    // 静态变量
    private static final HungarySingleton instance = new HungarySingleton();

    // 必须使用static,不加就需要调用方创建实例然后调用,而构造函数是私有的
    public static HungarySingleton getInstance(){
        return instance;
    }
}

    之所以成为“饿汉式”是因为在加载类的时候已经完成了静态变量的初始化。这个方式的优点就是不用考虑线程安全问题,缺点也很明显,如果一个对象初始化需要很长的时间而又没有被调用,就造成了资源浪费。

懒汉式

    又称为延迟加载,在需要调用时候才会去创建对象。

public class LazySingleton {

    private LazySingleton() {}
    // 使用静态变量记录类的唯一实例
    private static LazySingleton instance = null;

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

    以上代码在单线程环境下没有任何问题,但是在多线程环境下就会出现创建多个实例的问题,解决方法非常简单,使用同步即可,可以使用同步方法或同步代码块,又因为同步比较消耗性能,在判断instance为null时再使用同步代码块,改进后的代码如下

public class LazySingleton {

    private LazySingleton() {}
    // 使用静态变量记录类的唯一实例
    private static LazySingleton instance = null;

    public static LazySingleton getInstance(){
        if(instance == null){                    // 1
            // 同步代码块
            synchronized (LazySingleton.class) { // 2
                instance = new LazySingleton();  // 3
            }
        }
        return instance;
    }
}

    同步是非常浪费性能的,因为一次只能有一个线程执行,其它线程等待,所有在同步之前先判断instance是否为空,然后在同步。分析一下以上代码:有两个线程A和线程B,都是首次执行,假设现在线程A和B都执行到“代码1处”,继续向下执行,假设线程A获得锁执行同步代码块,线程B在“代码2处”等待,线程A执行到“代码3处”创建对象退出同步代码块并释放锁,此时已经创建了对象。然后线程B获得锁,执行“代码3处”再次创建对象,这就导致出现了多个对象。解决方法也很简单,在同步代码块内部做一次非空判断即可,这也是所谓的“双重检测”。

双重检测

public class DoubleSingleton {

    private volatile static DoubleSingleton instance;
    private DoubleSingleton(){}

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

    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10000; i++) {
            pool.submit(new Runnable() {

                @Override
                public void run() {
                    System.out.println(DoubleSingleton.getInstance().hashCode());
                }
            });
        }
        pool.shutdown();
    }
}   

    需要注意instance变量需要使用volatile关键词修饰,来保证在多线程下能够正确的处理instance变量,必须在jdk5之后才可以使用volatile关键字。假如没有volatile关键字修饰的话,会出现一个问题:在Java编译器中,JVM会对代码进行优化,也就是重排序,也就是说DoubleSingleton类的初始化和instance变量赋值顺序 不可预料,假如一个线程在没有同步化的条件下读取instance,并调用该对象的方法,可能对象的初始化还没有完成,从而造成程序错误。
一直没有模拟出来没有volatile关键字修饰程序会出现什么错误,希望看到的小伙伴不吝赐教!!


    可以看到使用饿汉式实现单例模式优点就是不用考虑多线程,缺点是比较占用内存。懒汉式方式比较繁琐,volatile也会降低程序的性能。那么有没有一种结合两者优点的方式呢?可以使用静态内部类来完成

静态内部类

public class InnerSingleton {

    private InnerSingleton(){}
    private static class Singleton{
        private static  InnerSingleton single = new InnerSingleton();
    }

    public static InnerSingleton getInstance(){
        return Singleton.single;
    }
}

    只有在调用getInstance()方法时才会创建对象,既保证了线程安全,又保证了延迟加载。

枚举方式

    《Effective Java》第三条:Java5之后,可以使用枚举实现单例,可以防止多次序列化以及反射攻击,同时又非常简洁,可以说是单例模式最佳的实现方式。

public enum EnumSingleton {

    INSTANCE;

    public void print(){
        System.out.println("I am EnumSingleton");
    }
}

实现多例模式

    下面对单例模式进行改造,使用缓存的思想模拟实现“多例模式”,既程序中一个类的实例对象可以存在有限多个(比如说3个),和单例模式唯一的区别就是程序中可以有多个实例对象

public class SomeSingleton {

    private SomeSingleton() {}
    // 存储创建的实例(模拟缓存)
    private static Map<Integer, SomeSingleton> map = new HashMap<>(3);
    // 计数器从1开始
    private volatile static int num = 1;
    // 控制最多有三个实例
    private static final int MAX_NUM = 3;
    // 锁
    final static ReentrantLock lock = new ReentrantLock();

    public static SomeSingleton getInstance() {
        lock.lock();
        try {
            // 从缓存中取出实例
            SomeSingleton instance  = map.get(num);
            if (instance == null) {
                instance = new SomeSingleton();
                // 计数器作为map的key
                map.put(num, instance);
            }
            // 计数器+1
            num++;
            if (num > MAX_NUM) {
                // 重置计数器
                num = 1;
            }
            return instance;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        final Set<Integer> set = new HashSet<>();
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10000; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    // System.out.println(getInstance().hashCode());
                    set.add(getInstance().hashCode());
                }
            });
        }
        System.out.println(set.size());
        pool.shutdown();
    }
}

    不管有多少个线程调用,都只会创建3个实例对象,完成了控制有限过个实例对象。

小结

    单例模式的实质就是控制实例对象在程序中的数量有且仅有一个,并且只能自己创建。实现单例可以使用“静态内部类”和“枚举”方式,不过“静态内部类”需要注意反射攻击以及序列化破坏。

JDK中的单例模式
    java.lang.Runtime
    java.text.NumberFormat
Spring中
    在Spring中默认的bean都是单例的

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单例模式多例模式设计模式中的两种常见实现方式。 单例模式是一种创建对象的模式,它保证一个类只有一个实例,并提供一个全局访问点来访问该实例。在C#中,可以通过使用静态变量和私有构造函数来实现单例模式。以下是一个示例: ```csharp public class Singleton { private static Singleton instance; private Singleton() {} // 私有构造函数 public static Singleton Instance { get { if (instance == null) { instance = new Singleton(); } return instance; } } } ``` 使用单例模式可以确保在整个应用程序中只有一个实例存在,并且可以通过`Singleton.Instance`来访问该实例。 多例模式是一种可以创建有限个数实例的模式,它保证一个类最多只有指定个数的实例,并提供一个全局访问点来访问这些实例。在C#中,可以使用类似于单例模式的方式来实现多例模式,只需稍作修改即可。以下是一个示例: ```csharp public class Multiton { private static Dictionary<string, Multiton> instances = new Dictionary<string, Multiton>(); private Multiton() {} // 私有构造函数 public static Multiton GetInstance(string key) { if (!instances.ContainsKey(key)) { // 创建新实例并添加到字典中 instances[key] = new Multiton(); } return instances[key]; } } ``` 使用多例模式可以创建指定个数的实例,并且可以通过`Multiton.GetInstance(key)`来访问这些实例,其中`key`是用于区分不同实例的标识符。 这是单例模式多例模式的简单介绍,它们在实际开发中有各自的应用场景,具体使用哪种模式取决于实际需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值