大型Java进阶专题(五) 设计模式之单例模式

前言

​ 今天开始我们专题的第四课了,最近公司项目忙,没时间写,今天抽空继续。上篇文章对工厂模式进行了详细的讲解,想必大家对设计模式合理运用的好处深有感触。本章节将介绍:单例模式与原型模式。本章节参考资料书籍《Spring 5核心原理》中的第一篇 Spring 内功心法(Spring中常用的设计模式)(没有电子档,都是我取其精华并结合自己的理解,一个字一个字手敲出来的)。

单例模式

单例模式的应用场景

​ 单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛。例如,国家主席、公司CEO、部门经理等。在 J2EE 标准中,ServletContext、ServletContextConfig等;在Spring框架应用中ApplicationContext;数据库的连接池也都是单例形式。

饿汉式单例

先看下单例模式的类结构图:

​ 饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题。
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅坑不拉屎。
​ Spring中IOC容器ApplicationContext本身就是典型的饿汉式单例。接下来看一段代码:

public class HungrySingleton {
    //类加载顺序:先静态、后动态
    //先属性、后方法
    //先上后下
    private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return HUNGRY_SINGLETON;
    }
}

调用过程:当你第一次调用HungrySingleton.getInstance()时,类加载器会加载改对象,会先初始化心态属性,也就是执行了新建一个HungrySingleton对象,再加载静态方法getInstance(),返回的就是刚刚新建的对象。只有再调用都会直接返回了。

​ 此外还有另外一种写法,使用静态代码块:

public class HungrySingleton {
    //类加载顺序:静态代码块=》静态属性=》静态方法
    private static final HungrySingleton HUNGRY_SINGLETON;

    static {
        HUNGRY_SINGLETON = new HungrySingleton();
    }
    
    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return HUNGRY_SINGLETON;
    }
}

​ 这两种写法都非常的简单,也非常好理解,饿汉式适用在单例对象较少的情况。下面我们来看性能更优的写法。

懒汉式单例

懒汉式单例的特点是:被外部类调用的时候内部类才会加载,下面看懒汉式单例的简单实现LazySimpleSingleton:

//当外部使用时才会实例化
public class LazySimpleSingleton {
    //静态块,公共内存区域
    private static LazySimpleSingleton LAZY_SINGLETON;

    public static LazySimpleSingleton getInstance() {
        if (LAZY_SINGLETON == null) {
            LAZY_SINGLETON = new LazySimpleSingleton();
        }
        return LAZY_SINGLETON;
    }

    private LazySimpleSingleton() {
    }
}

创建线程类:

public class ExectorThread implements Runnable{
    @Override
    public void run() {
        LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":" + singleton);
    }
}

客户端测试代码:

public class LazySimpleSingletonTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new ExectorThread());
        Thread t2 = new Thread(new ExectorThread());
        t1.start();
        t2.start();
        System.out.println("End");
    }
}

调用结果:

End
Thread-1:com.study.demo.LazySimpleSingleton@20cf7200
Thread-0:com.study.demo.LazySimpleSingleton@39b6c48f

一定几率出现创建两个不同结果的情况,意味着上面的单例存在线程安全隐患。这是因为两个线程同时执行的了,调用方法发现实例都还没来得及创建,两个线程就分别都创建了一个实例。有时,我们得到的运行结果可能是相同的两个对象,实际上是被后面执行的线程覆盖了,我们看到了一个假象,线程安全隐患依旧存在。那么,我们如何来优化代码,使得懒汉式单例在线程环境下安全呢?来看下面的代码,给getInstance()加上synchronized关键字,是这个方

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值