跳转至

单例模式

1. 介绍

  单例模式(Singleton Pattern)是 Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

  这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

特点

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。
  • 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 主要解决:一个全局使用的类频繁地创建与销毁。
  • 何时使用:当您想控制实例数目,节省系统资源的时候。
  • 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
  • 关键代码:构造函数是私有的。
  • 使用场景: 要求生产唯一序列号,WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来,或者创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

2. 示例

饿汉式

package com.cmz.singleton;

/**
 * 饿汉式
 * 类加载到内存后,就实例化一个单例,JVM保证线程安全
 * 简单实用,推荐使用!
 * 唯一缺点:不管用到与否,类装载时就完成实例化
 * Class.forName("")
 * (话说你不用的,你装载它干啥)
 */
public class Mgr01 {

    //创建 Mgr01 的一个对象
    private static final Mgr01 INSTANCE = new Mgr01();

    //让构造函数为 private,这样该类就不会被实例化
    private Mgr01() {};

    //获取唯一可用的对象
    public static Mgr01 getInstance() {
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        Mgr01 m1 = Mgr01.getInstance();
        Mgr01 m2 = Mgr01.getInstance();
        System.out.println(m1 == m2);
    }
}
静态代码块
package com.cmz.singleton;

/**
 * 跟01是一个意思,只不过是静态代码带而已
 */
public class Mgr02 {
    private static final Mgr02 INSTANCE;
    static {
        INSTANCE = new Mgr02();
    }

    private Mgr02() {};

    public static Mgr02 getInstance() {
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        Mgr02 m1 = Mgr02.getInstance();
        Mgr02 m2 = Mgr02.getInstance();
        System.out.println(m1 == m2);
    }
}
懒汉模式
package com.cmz.singleton;

/**
 * lazy loading
 * 也称懒汉式,线程不安全
 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题,这种方式是最基本的实现方式,
 * 这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
 * 这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
 */
public class Mgr03 {
    private static Mgr03 INSTANCE;

    private Mgr03() {
    }

    public static Mgr03 getInstance() {
        if (INSTANCE == null) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Mgr03();
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->
                System.out.println(Mgr03.getInstance().hashCode())
            ).start();
        }
    }
}
加锁的模式1: 懒汉模式
package com.cmz.singleton;

/**
 * lazy loading
 * 也称懒汉式
 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题
 * 可以通过synchronized解决,但也带来效率下降
 */
public class Mgr04 {
    private static Mgr04 INSTANCE;

    private Mgr04() {
    }

    public static synchronized Mgr04 getInstance() {
        if (INSTANCE == null) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Mgr04();
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr04.getInstance().hashCode());
            }).start();
        }
    }
}
加锁的模式2: 懒汉模式
package com.cmz.singleton;

/**
 * lazy loading
 * 也称懒汉式
 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题
 * 可以通过synchronized解决,但也带来效率下降
 */
public class Mgr05 {
    private static Mgr05 INSTANCE;

    private Mgr05() {
    }

    public static Mgr05 getInstance() {
        if (INSTANCE == null) {
            //妄图通过减小同步代码块的方式提高效率,然后不可行
            synchronized (Mgr05.class) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Mgr05();
            }
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr05.getInstance().hashCode());
            }).start();
        }
    }
}
加锁的模式3: 懒汉模式
package com.cmz.singleton;

/**
 * lazy loading
 * 也称懒汉式
 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题
 * 可以通过synchronized解决,但也带来效率下降
 */
public class Mgr06 {
    private static volatile Mgr06 INSTANCE; //JIT

    private Mgr06() {
    }

    public static Mgr06 getInstance() {
        if (INSTANCE == null) {
            //双重检查
            synchronized (Mgr06.class) {
                if(INSTANCE == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr06.getInstance().hashCode());
            }).start();
        }
    }
}
静态内部类
package com.cmz.singleton;

/**
 * 静态内部类方式
 * JVM保证单例
 * 加载外部类时不会加载内部类,这样可以实现懒加载
 */
public class Mgr07 {

    private Mgr07() {
    }

    private static class Mgr07Holder {
        private final static Mgr07 INSTANCE = new Mgr07();
    }

    public static Mgr07 getInstance() {
        return Mgr07Holder.INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr07.getInstance().hashCode());
            }).start();
        }
    }
}

  这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。 这种方式是 Effective Java 作者 Josh Bloch提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。 不能通过 reflection attack 来调用私有构造方法。

枚举法
package com.cmz.singleton;

/**
 * 不仅可以解决线程同步,还可以防止反序列化。
 */
public enum Mgr08 {

    INSTANCE;

    public void m() {}

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr08.INSTANCE.hashCode());
            }).start();
        }
    }

}

其实后面我们可以使用spring 来管理,使用bean工厂来保证单例。