【多线程之单例模式】


前言

在本篇博客中,我们将讲述一下单例模式是什么,它的优缺点,还有如何创建一个单例模式


一、什么是单例模式

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

有三个要点

  • 该类只有一个实例
  • 该类自行创建这个实例
  • 它必须自行向整个系统提供这个实例

单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.

这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个.

二、单例模式的优缺点

优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2、避免对资源的多重占用(比如写文件操作)。

缺点:
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

三、单例模式之饿汉模式(创建 )

代码如下

//单例模式(饿汉模式),只能实例一个,把构造方法设置为私有,且自身线程安全,因为只有读操作
class Singleton{
    private static Singleton instence = new Singleton();           //static修饰表示该对象是类对象,即唯一实例
    private Singleton(){}
    public static Singleton getInstance(){
        return instence;
    }
}
public class Demo9 { 
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
    }
}

这是饿汉模式的创建:

这个饿汉模式的饿主要体现在该类被加载时就会创建一个对象(而懒汉模式不会(用到才实例化一个)),但是懒汉模式也有它的优点,即代码简单,不会有线程安全问题,可以保证实例的唯一性,不过它的缺点就是类加载的时候就创建对象,如果系统不需要使用该单例对象,又由于类加载时就会创建,所以有可能会造成资源利用效率不行,而懒汉模式就不会出现这种

四、单例模式之懒汉模式

代码如下

class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
       }
        return instance;
   }
}

这是懒汉模式的创建(单线程)

从上面代码,我们可以看到,在类加载的时候并不会立即创建实例,只有第一次使用该类的时候才会创建对象,无需占用资源,比饿汉模式资源利用率更好,但是因为这个好处也诞生了线程安全问题,比如上述代码,只能适用于单线程,如果在多线程下,那么就会出现线程安全问题了
例如:对象还没创建,当两个线程同时都进入了if判断里,岂不是创建了两个

解决代码如下

class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
       }
        return instance;
   }
}

我们只需要对getInstance()方法加锁即可解决,这样就可以解决了,保证一次只有一个线程能进入方法。解决了线程安全问题,但是呢,我们可以想想,该代码是否还能优化吗?

从该模式来说,当第一个实例化创建之后,其他就不会再创建,而是返回该单例,即再也进不了if,可是每次方法都加锁了,影响了性能,(所以有了优化方向,只需要对if加锁)

代码如下

//懒汉模式,需要几个就创建几个,不着急,最开始不初始化实例,用到才初始化,
class Singleton2{

    private static volatile Singleton2 instence = null;   //volatile 禁止指令重排序
    private Singleton2(){

    }
    public static Singleton2 getInstance(){
        if(instence == null) {   //判断是否需要加锁
            synchronized (Singleton2.class) {   //涉及读和修改操作,需要加锁才线程安全  类对象(只有一个)
                if (instence == null) {   //判断是否要修改  (读)
                    instence = new Singleton2();   // (修改)  这里会出现指令重排序问题(加volatile)  1.创建内存2.赋予空间(属性赋值等)3.付给引用
                }
            }
        }
        return instence;
    }
}

上述代码,我们可以看出有两个if判断,这也就是懒汉模式单例模式中的双重if校验,接下来讲讲这两个if的作用,第一层if它的目的是优化,毕竟我们只需要实例化一次,用来判断是否需要加锁,比如当多线程情况下,有两个线程同时判断是否为空,都进入了,这时候就需要竞争锁了,线程1进入了,然后线程1成功实例化,释放锁,当线程2再进入发现已经实例化了,就进入不了if了。然后以后就不需要加锁了,因为再也进不了第一层if了(优化(达到只加锁一次))

在上述代码还有一个重点,即 instence 使用volatile修饰,这个volatile修饰是为了防止在实例化对象的时候禁止指令重排序,正如上述代码中注释,当顺序为1->3->2;假如在3->2中间,有其他线程使用该对象就会出现问题,因为对象还没有实例成功。(是否也是为了保证内存可见性存疑)

对比上述两种单例模式,我们发现饿汉模式会出现实例时机太早,万一用不到,则会造成资源利用率下降。懒汉模式虽然解决了资源利用效率,但是必须处理多个线程访问的问题,需要通过双重检查锁定等机制进行控制,导致系统性能受到一定影响。有没有方法综合二者的有点呢?当然有,下面将为大家介绍

五、单例模式之懒汉模式(用静态内部类)

这种方法被称之为Initialization on Demand Holder (IoDH)的技术。
在IoDH中,我们在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance()方法返回给外部使用,实现代码如下所示:

//静态内部类实现的单例模式(懒汉)
class Singleton{
    private Singleton(){
    }

    private static class SingletonHolder {
        private static final Singleton SINGLETON = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.SINGLETON;
    }
}

public class Main {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
    }
}

通过使用IoDH,我们既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为一种最好的Java语言单例模式实现方式(其缺点是与编程语言本身的特性相关,很多面向对象语言不支持IoDH)。


  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值