设计模式-单例模式

一、定义

单例模式算是比较简单的一种设计模式了,其核心思想就是确保一个类只有一个实例,并提供一个全局访问点。

具体点来说,就是我们把单例类设计成自己管理的一个单独实例,同时避免其他类创建单例实例,要想获取单例实例,通过单例类是唯一的方式。所谓的全局访问点,单例实例提供给其他类的获取实例的方法,也就是在这个方法中,保证了单例类只有一个实例。

二、使用场景

有一些对象其实我们只需要一个,比如:线程池、缓存、日志对象等等,这些对象只能有一个实例,如果有多个,会导致很多问题,比如程序异常、资源使用过量等。

三、实现

单例模式实现的核心就是将构造器私有化,这样一来只有单例自身才能创建对象。

线程不安全的实现

java 复制代码
public class SimpleSingleton {
    //单例实例
    private static SimpleSingleton instance;
    //获取单例实例,如果不存在就创建
    public static SimpleSingleton getInstance(){
        if(instance == null){
            instance = new SimpleSingleton();
        }
        return instance;
    }

    private SimpleSingleton(){
        System.out.println(Thread.currentThread().getId() + "," + System.currentTimeMillis() + ",创建了SimpleSingleton");
    }
}

这样实现起来也很简单,在一般情况下是没有问题的,但是在多线程的情况下,就会出现问题。

java 复制代码
public class SingletonTest {

    public static void main(String[] args) {
       testSimpleSingletonTask();
    }

    static void testSimpleSingletonTask(){
        ExecutorService pool = Executors.newFixedThreadPool(10);
        SimpleSingletonTask simpleSingletonTask = new SimpleSingletonTask();
        pool.execute(simpleSingletonTask);
        pool.execute(simpleSingletonTask);
        pool.execute(simpleSingletonTask);
        pool.execute(simpleSingletonTask);
        pool.execute(simpleSingletonTask);
        pool.execute(simpleSingletonTask);
        pool.shutdown();
    }
    static class SimpleSingletonTask implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getId() + "," + SimpleSingleton.getInstance());
        }
    }
}

输出结果:

可以看到,在多线程的情况下,每个线程都创建了一个单例实例,这肯定不是我们想要的结果,那为什么会这样呢?

其实如果对多线程编程有了解的话很容易看出来,单例类中的instance是一个共享变量,而getInstance中提供了对这个共享变量的修改,但修改是线程不安全的,很容易在多个线程执行这个方法的时候,都判断变量为空,然后都创建了一个实例赋值。

线程安全的单例模式

上述线程不安全的原因就是对共享变量instance修改时没有考虑多线程的情况,那最简单的解决方法就是直接给getInstancesynchronized,改为下面这样:

java 复制代码
public class Singleton {
    //共享变量加上volatile
    private volatile static Singleton instance;

    private Singleton(){
        System.out.println(Thread.currentThread().getId() + "," + System.currentTimeMillis() + ",创建了Singleton");
    }
    //直接加synchronized
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }

}

这样肯定可以解决问题,但实际上,我们只有在第一次创建实例的时候需要加同步锁,在之后的时间里,实例已经存在了,就没有必要加锁了,这样会影响效率。

所以就有了下面这样的解决方法,双重校验锁:

java 复制代码
public class Singleton {

    private volatile static Singleton instance;

    private Singleton(){
        System.out.println(Thread.currentThread().getId() + "," + System.currentTimeMillis() + ",创建了Singleton");
    }

    public static Singleton getInstance(){
        if(instance == null){
            //如果为空了,再去加锁创建实例
            synchronized(Singleton.class){
                //获取之后再判断一下,双重校验。原因是因为多线程的情况下,一个线程获取到了锁进来执行,其他的线程等待获取锁,释放锁后,其他的线程会陆续获取锁再次进来,所以获取锁之后还需要再判断一下是否已经创建了
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

我们用下面的代码测试:

java 复制代码
public class SingletonTest {

    public static void main(String[] args) {
        testSafeSingletonTask();
    }
    static void testSafeSingletonTask(){
        ExecutorService pool = Executors.newFixedThreadPool(10);
        SafeSingletonTask safeSingletonTask = new SafeSingletonTask();
        pool.execute(safeSingletonTask);
        pool.execute(safeSingletonTask);
        pool.execute(safeSingletonTask);
        pool.execute(safeSingletonTask);
        pool.execute(safeSingletonTask);
        pool.execute(safeSingletonTask);
        pool.shutdown();
    }

    static class SafeSingletonTask implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getId() + "," + Singleton.getInstance());
        }
    }

}

结果,只有一个线程创建了实例,其他线程获取到的都是这个实例

相关推荐
张小姐的猫16 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
杨充19 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充19 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充19 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充19 小时前
2.面向对象的特性
设计模式
杨充19 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充19 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充19 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯1 天前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法