Java设计模式之单例模式

设计模式之单例模式

  单例模式确保某一个类只有一个实例,而且自行实例化并且向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

单例的特点
  • 某个类只能有一个实例
  • 它必须自行创建这个实例
  • 它必须自行向整个系统提供这个实例
  • 单例模式是一种对象创建型模式
单例模式结构图

  单例模式只包含一个Signgleton(单例角色),同时提供一个静态的getInstance()工厂方法,然后应用获取它的唯一实例;为了防止外部对其实例化,需要将构造方法设计为私有。
在这里插入图片描述

一般的单例模式实现

  一般的单例模式,代码如下:

public class Singleton {
    /*静态的变量声明*/
    private static Singleton instance;

    /*构造方法为private这样可以堵死外部使用new来创建对象的可能*/
    private Singleton(){

    }

    /*静态工厂方法,是本类实例的唯一访问方法*/
    public static Singleton getInstance(){
        //如果实例不存在则创建一个
        if (instance == null){
            System.out.println("开始初始化...");
            instance = new Singleton();
        }
        return instance;
    }
}
多线程下的单例模式

  关于上面的单例模式,在多线程下,是会存在问题的,会出现创建多个Singleton实例,下列是一个多线程调用的实例,100个线程并发调用Singleton的getInstance(),如果有问题则会出现调用多次初始化的方法:

public class MultiThreadTest {
    public static void main(String[] args) {
        //使用CountDownLatch来模拟100个线程同时并发
        final CountDownLatch countDownLatch = new CountDownLatch(100);
        for (int i= 0; i  < 100; i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //计数减去1
                    countDownLatch.countDown();
                    try {
                        //await等待到CountDownLatch的计数为0时再唤醒
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Singleton.getInstance();
                }
            }).start();
        }
    }
}

  运行后结果如下,每一次运行的结果都可能不一样,下面是跑了测试例子之后的结果,实例被创建了6次,显然上面的单例模式在多线程下是存在问题:
在这里插入图片描述

  面对多线程的单例的问题,需要针对初始化的代码添加锁来实现,保证同一时刻只有一个线程进入初始化的代 码块,在一个线程初始化后,则其他线程再走到instance==null这个条件是就不会进入分支了。Java中第一种方式,在方法上添加synchronized关键字:

public class Singleton {
    /*静态的变量声明*/
    private static Singleton instance;

    /*构造方法为private这样可以堵死外部使用new来创建对象的可能*/
    private Singleton(){

    }

    /*静态工厂方法,是本类实例的唯一访问方法,在方法上添加synchronized关键字加锁*/
    public synchronized static Singleton getInstance(){
        //如果实例不存在则创建一个
        if (instance == null){
            System.out.println("开始初始化...");
            instance = new Singleton();
        }
        return instance;
    }
}

  通过方法上添加synchronized关键字加锁的方式,粒度过于大了,可以通过过代码块加锁的方式,缩小加锁的范围,并且通过双重检查,减小加锁的范围,在外层先通过if(instance == null)先判断是否为null,如果不为null则不会加锁,这降低了锁的开销,较小性能开销,这种方式比较推荐的:

public class Singleton {
    /*静态的变量声明*/
    private static Singleton instance;

    /*构造方法为private这样可以堵死外部使用new来创建对象的可能*/
    private Singleton(){

    }

    /*静态工厂方法,是本类实例的唯一访问方法*/
    public static Singleton getInstance(){
        //如果实例不存在则创建一个
        if (instance == null){
            synchronized (Singleton.class){
                //采取双重锁定的方式
                if (instance == null){
                    System.out.println("开始初始化...");
                    instance = new Singleton();
                }
            }

        }
        return instance;
    }
}
静态化初始化的单例模式

  这种方式单例模式也被称为饿汉式的单例类,为什么这么说呢?因为它在类被加载进JVM时就将自己实例化了,哈哈,形象生动。前面的几种单例类实现都是懒汉式单例类,因为它们都是在需要时才真正实例化。下面是静态初始化的单例模式:

public class Singleton {
    /*静态的变量声明,直接初始化了,懒汉式单例*/
    private static Singleton instance = new Singleton();

    /*构造方法为private这样可以堵死外部使用new来创建对象的可能*/
    private Singleton(){

    }

    /*静态工厂方法,是本类实例的唯一方位方法*/
    public static Singleton getInstance(){
        return instance;
    }
}
什么场景下使用单例模式

  如果我们的类的初始化的开销非常大,且每一次初始化都是一样,也即是每次实例其实都是状态一致的实例,或者多个实例同时存在是会引发错误时,那么该类就应该设计成单例模式。典型的单例应用有:数据库的连接池的连接,Hibernate的SessionFactory实现。

单例模式优缺点

优点

  • 提供了对唯一实例的受控访问
  • 由于系统中只有一个对应的实例,故可以节省资源,减少创建对象和销毁的开销

缺点

  • 由于单例中没有抽象层,故单例类的扩展比较困难
  • 单例类的职责过重了,它既充当了工厂角色,提供了工厂方法getInstance(),又充当了产品角色,包含业务方法。不过这个工厂方法,是单例的访问入口,也是必须的,故也不算什么缺点,只能说是和某些设计模式的原则有冲突而已
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值