【Java】从零开始学设计模式:单例模式

10 篇文章 0 订阅

介绍

单例模式,一般是指类的单例模式,采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。

用途:类的初始化过程消耗太大,或者该类实例比较消耗资源,采用单例模式,减少资源占用。

种类

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程安全,同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举

饿汉式(静态常量)

步骤:

  1. 构造器私有化:防止在外部通过new来创建
  2. 类内部创建对象
  3. 暴露静态方法,返回刚创建的对象
public class Singleton1 {

    public final static Singleton1 INSTANCE = new Singleton1();

    private Singleton1() {
    }
 
    public static Singleton1 getInstance() {
        return INSTANCE;
    }
}

优点

  • 写法简单,在类装载的时候完成实例化,避免线程安全问题

缺点

  • 类加载过程完成实例化,没有达到懒加载的效果;如果软件生命周期内始终未使用过该对象,会造成内存浪费

饿汉式(静态代码块)

步骤:

  1. 构造器私有化:防止在外部通过new来创建
  2. 类内部创建static代码块来初始化对象
  3. 暴露静态方法,返回刚创建的对象
public class Singleton2 {

    public static Singleton2 INSTANCE;

    static {
        INSTANCE = new Singleton2();
    }

    private Singleton2() {
    }

    public static Singleton2 getInstance() {
        return INSTANCE;
    }
}

懒汉式(线程不安全)

步骤:

  1. 构造器私有化:防止在外部通过new来创建
  2. 暴露静态方法,当使用到该方法的时候,才去创建对象
public class Singleton3 {

    public static Singleton3 INSTANCE;

    private Singleton3() {
    }

    public static Singleton3 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton3();
        }
        return INSTANCE;
    }
}

优点

  1. 起到懒加载的效果

缺点

  1. 线程不安全:多线程环境下,当有一个线程t1进入到 if (INSTANCE == null) 代码块的时候,还没有把新创建的实例赋值给INSTANCE,另外一个线程t2也进入了if代码块,则t1和t2各自会创建一个实例赋值给INSTANCE,这就不是单例了

是否可用

实际工作中,不要使用

懒汉式(线程安全,同步方法)

既然上述的方式存在安全问题,那我们是否可以通过在方法加锁的方式来解决呢 ?请看下面的写法:

public class Singleton4 {

    public static Singleton4 INSTANCE;

    private Singleton4() {
    }

    public static synchronized Singleton4 getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton4();
        }
        return INSTANCE;
    }
}

优点

  1. 解决了线程安全问题

缺点

  1. 效率问题:每一次调用getInstance 方法的时候,都需要同步等待。而实际这个方法只需要执行一次实例化代码即可,后面的时刻想获得该实例,直接返回即可。

是否可用

实际工作中,不推荐使用。

懒汉式(线程不安全,同步代码块)

既然上述的方式效率太低,我们是不是可以把同步方法范围缩小到方法里的一个代码块呢 ? 于是有了下面的这种写法:

public class Singleton5 {

    public static Singleton5 INSTANCE;

    private Singleton5() {
    }

    public static Singleton5 getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton5.class) {
                INSTANCE = new Singleton5();
            }
        }
        return INSTANCE;
    }
}

优点

  1. 相对于同步整个getInstance 方法而言,效率有些许提升

缺点

  1. 线程安全问题:这么写本来是希望通过缩小同步范围(相对上一个实现而言),但是如果一个线程t3进入到if判断代码块的时候,还没有执行实例初始化并赋值给INSTANCE的时候,另外一个线程t4也进入到了if代码块,他们会分别对INSTANCE赋值,也就不是单例了。

是否可用

实际工作中,不能使用

双重检查

因为上个实现,存在线程安全问题,那么是否可以进行改进呢?请看下面的方式:

public class Singleton6 {

    public static volatile Singleton6 INSTANCE;

    private Singleton6() {
    }

    public static Singleton6 getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton6.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton6();
                }
            }
        }
        return INSTANCE;
    }
}

优点

  1. 达到懒加载的目的,线程安全,效率较高

简单分析一下为何线程是安全的:

  • 多线程情况下,如果线程t5/t6同时进入到了第一个if代码块,这个时候还没有实例化
  • 由于if代码块(第一个)内存在synchronized同步, 只能有一个线程(假设是t5)进入到第二个if代码块
  • 判断未实例化,然后创建新对象并赋值给 INSTANCE
  • 又由于volatile 关键字(详细解析请看最后的延伸阅读)修饰,这个时候另外一个线程t6也可以获取到该对象的值被更新
  • 当 t6 进入到第二个if的时候,判断INSTANCE 已经不是 null了,直接返回t5线程初始化的对象
  • 其他的线程(t7/t8…)再来调用getInstance方法的时候,判断INSTANCE 已经不是 null了,直接返回即可

是否可用

实际工作中,推荐使用

静态内部类

如果想要实现线程安全,是否可以让JVM帮我们达到这个目的呢?下面介绍的这个方式就是借助JVM装载类来保证线程安全

public class Singleton7 {
    
    private Singleton7() {
    }

    private static class SingletonIns{
        private static final Singleton7 INSTANCE = new Singleton7();
    }

    public static Singleton7 getInstance() {
        return SingletonIns.INSTANCE;
    }
}

优点

  1. 懒加载:静态内部类在Singleton7这个类被装载的时候,并不会立刻实例化,而是在需要实例化的地方,调用getInstance方法后,才装载内部类SingletonIns类,从而完成Singleton7实例化
  2. 线程安全:类的静态属性,只在第一次加载类的时候初始化,所以这里的线程安全是由JVM保证

是否可用

实际工作中,推荐使用

枚举

public enum Singleton8 {

    INSTANCE;

    public void func(){
        // 你的方式实现
    }
}

优点

  1. JDK1.5添加的枚举类来实现单例模式,避免线程安全问题,还能防止反序列化重新创建新的对象
  2. Effective Java作者Josh Bloch 所提倡的方式

是否可用

实际工作中,推荐使用!

延伸阅读

懒加载 (也叫 延迟加载惰性加载

而对于数据结构而言,惰性加载是指从一个数据对象通过方法获得里面的一个属性对象时,这个对应对象实际并没有随其父数据对象创建时一起保存在运行空间中,而是在其读取方法第一次被调用时才从其他数据源中加载到运行空间中,这样可以避免过早地导入过大的数据对象但并没有使用的空间占用浪费。

volatile
推荐阅读 Volatile的作用

volatile关键字可以让线程的修改立刻通知其他的线程,从而达到数据一致的作用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值