单例模式&多例模式

来源:互联网 发布:整理相册的软件 编辑:程序博客网 时间:2024/04/29 12:10

初识单例模式

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

  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都是单例的

原创粉丝点击