设计模式之单例模式

104 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情

简介

单例模式的定义是: 保证一个类仅有一个实例,并提供一个该实例的全局访问点

为什么要使用单例模式:

1、减少对象频繁创建和回收的开销,提高系统性能

2、为全局的访问提供统一入口,方便管理和维护

案例

通过收集各种资料,我这里将列举7种实现单例模式的方法,它们各有优缺点,下面咱们一个个举例分析:

1、使用静态类

public class Singleton {
    public static Map<String,String> instance = new ConcurrentHashMap<String, String>();
}

使用静态类的变量也是一种单例,在第一次运行的时候加载初始化Map,单缺点是这种方式无法使用延迟加载。

2、懒汉式(线程不安全)

public class Singleton {
    private static Map<String,String> instance;
    private Singleton() {
    }
    public static Map getInstance(){
        if (null != instance) return instance;
        instance = new ConcurrentHashMap<String, String>();
        return instance;
    }
}

这种方式是比较常规的懒汉式,虽然是延迟加载的,但是并不是线程安全的,在多线程情况下instance可能并不是单例的。

3、懒汉式(线程安全)

public class Singleton {
    private static Map<String,String> instance;
    private Singleton() {
    }
    public static synchronized Map getInstance(){
        if (null != instance) return instance;
        instance = new ConcurrentHashMap<String, String>();
        return instance;
    }
}

这种方式是对第二种方式的升级版,通过给创建实例的方法增加synchronized锁来确保线程安全,在多线程环境下返回的始终是同一个实例,而且同样满足延迟加载的。缺点就是在整个方法上加锁性能上可能会有一定的影响,故一般情况下我们不推荐使用这种方式。

4、饿汉式

public class Singleton {
    private static Map<String,String> instance = new ConcurrentHashMap<String, String>();
    private Singleton() {
    }
    public static Map<String,String> getInstance() {
        return instance;
    }
}

这种方式是典型的饿汉式,顾名思义,饿汉就是很饥饿,很急迫地想要拿到实例,即类第一次初始化就创建了。这种方式是线程安全的,但是并不满足延迟加载。可能会造成资源浪费。但是如加载的类并不大,而且能预见到后续肯定会用到这个单例,那这种方式还是可取的。这种方式与第一种比较类似。

5、使用类的内部类

public class Singleton {
    private static class SingletonHolder {
        private static Map<String, String> instance = new ConcurrentHashMap<String, String>();
    }
    private Singleton() {
    }
    public static Map<String, String> getInstance() {
        return SingletonHolder.instance;
    }
}

通过静态内部类这种方式也是比较少见的,但是它却是非常好的一种方式:既是懒加载的,又满足线程安全,而且也不浪费资源。我之前也没用过这种方式,以后可以在实战中尝试。

6、双重锁校验

public class Singleton {
    private static volatile Map<String, String> instance;
    private Singleton() {
    }
    public static Map<String, String> getInstance(){
        if(null != instance) return instance;
        synchronized (Singleton.class){
            if (null == instance){
                instance = new ConcurrentHashMap<String, String>();
            }
        }
        return instance;
    }
}

这种就非常经典的了,记得有次面试题就是手写这个方式。这个也是实战中比较推荐的一种方式,比较严谨,而且不会太占用资源。

7、枚举

public enum Singleton {
    INSTANCE;
    public void dosth(){
        System.out.println("do sth......");
    }
}

枚举这种方式听说得还是比较多的,墙外的大神倒是非常推崇,但是在我的经验中还没见过谁在实战中使用过,具体的原因我也不明白,以后在实战中也可以多研究研究。

总结

这里列举了7种方式,推荐后面三种在实战种运用。其实除了这些方法之外,在其他资料里还有通过CAS等方式实现单例,但是不太好理解,用的也不多,我就不在此详述,感兴趣的可以去查阅。

单例模式在工作中还是使用得比较多的,关注源码的同学可能在比较多的源码中均能看到它的使用,所以它虽然简单,但是很实用,花点力气掌握还是必要的。