二、java设计模式之单例模式

1. 单例模式

 单例模式是设计设计模式中使用最为普遍的模式之一,它是一种对象创建模式。用于产生一个对象的具体实例,可以确保系统中一个类只产生一个实例

单例模式的定义

  • 单例模式(Singleton Pattern)是一个比较简单的模式,其定义如下:
  • Ensure a class has only one instance, and provide a globalpoint of access to it.(确保某一个类只有一个实例, 自行实例化并向整个系统提供这个实例。)

使用单例模式的优缺点

优点:

  • 由于单例模式只生成一个实例,所以减少了系统的性能开销。(特别是对于频繁创建和销毁或者重量级对象)

  • 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。

  • 单例模式可以减轻GC的压力,缩短GC停顿时间。

  • 减少对内存的占用。
    缺点:

  • 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。(违背开闭原则,对扩展开放,对修改关闭)

  • 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。

2. 线程不安全的单例

/**
 * 最简单的单例
 */
public class SimpleSingleton {

    private static SimpleSingleton instance;

    private SimpleSingleton() {
        System.out.println("我是独生子,我是帮程序员爸爸/妈妈做事的。");
    }

    /**
     * 获取单例对象
     * @return
     */
    public static SimpleSingleton getInstance() {
        if (instance == null) {
            instance = new SimpleSingleton();
        }
        return instance;
    }

    /**
     * 处理其他业务的方法
     */
    public void doSomething(){
        System.out.println("这些事情只有我能做");
    }

}

测试:

    @Test
    public void test01(){
        //获取对象
        SimpleSingleton instance = SimpleSingleton.getInstance();
        //处理其他业务
        instance.doSomething();
    }

运行结果:
在这里插入图片描述

特点

上述代码有两个特点:

  1. 所有的单例构造器都要被声明为私有的(private)
  2. 通过声明静态(static)方法实现全局访问获得该单例实例。

线程不安全

 上述代码在并发量比较小的程序中基本上是不会有问题的。但是在高并发情况下,这段代码就会产生线程安全问题。

 我们对代码稍作修改。

/**
 * 最简单的单例
 */
public class SimpleSingleton {

    private static SimpleSingleton instance;

    private SimpleSingleton() {
        System.out.println(Thread.currentThread().getName()+":我是独生子,我是帮程序员爸爸/妈妈做事的。");
    }

    /**
     * 获取单例对象
     * @return
     */
    public static SimpleSingleton getInstance(){
        try {
            if (instance == null) {
                //模拟一个耗时比较长的创建过程
                Thread.currentThread().sleep(1000);
                instance = new SimpleSingleton();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return instance;
    }

    /**
     * 处理其他业务的方法
     */
    public void doSomething(){
        System.out.println("这些事情只有我能做");
    }

}

主要变化实在判空后,创建对象之前添加了Thread.currentThread().sleep(1000);来模拟一个复杂且耗时较长的对象创建过程。
测试:

       @Test
    public void test02() throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            //获取对象
            SimpleSingleton instance = SimpleSingleton.getInstance();
            //处理其他业务
            instance.doSomething();
            System.out.println(instance);
        });

        Thread thread2 = new Thread(() -> {
            //获取对象
            SimpleSingleton instance = SimpleSingleton.getInstance();
            //处理其他业务
            instance.doSomething();
            System.out.println(instance);
        });
        //模拟并发
        thread1.start();
        thread2.start();
        
        //等待其他线程结束
        Thread.currentThread().sleep(2000);
        System.out.println(SimpleSingleton.getInstance());
    }

在这里插入图片描述

  1. 在程序开始时,由于之前getInstance方法没有被调用过,instance为null。
  2. 因为业务上的需要,thread1和thread2都需要获取instance,并通过instance的doSomething方法来执行一些业务操作。thread1和thread2几乎同时调用了getInstance方法。
  3. instance 为null,第一个线程需要创建一个实例。由于创建实例的过程比较复杂、耗时比较长。在第二个线程执行判断if (instance == null) 时,对象实例没有被创建出来,instance 也为null,所以第二个线程也需要创建一个实例。
  4. 一个单例对象被创建了两次,这显然不合理。违背了单例模式的定义(确保某一个类只有一个实例)。
			if (instance == null) {
                //模拟一个耗时比较长的创建过程
                Thread.currentThread().sleep(1000);
                instance = new SimpleSingleton();
            }
  • 为了就解决线程安全的问题,产生了饿汉式单例懒汉式单例

3.饿汉式单例

public class EHSingleton {

    private static final EHSingleton instance = new EHSingleton();

    private EHSingleton() {
        System.out.println("我才是真正的的独生子");
    }

    /**
     * 获取单例对象
     * @return
     */
    public static EHSingleton getInstance(){
        return instance;
    }

    /**
     * 处理其他业务的方法
     */
    public void doSomething(){
        System.out.println("这些事情只有我能做");
    }

}

测试

    @Test
    public void test03(){
        //我是饿汉式,我要提前加载
        EHSingleton instance = EHSingleton.getInstance();
        instance.doSomething();
    }

在这里插入图片描述

  • 为了解决线程安全问题,一共有两种方案,其中一种就是在类加载时就把单例实例创建出来(饿汉式单例)。实例创建之迫切,就像一个饿汉看到这碗面有大又圆……
  • 饿汉式的特点:
    • 类加载是就创建实例(没有进行懒加载)
    • 避免了懒汉式使用同步锁和判断实力是否 创建的额外检查,执行效率高
    • 线程安全

4.懒汉式单例

单检锁的懒汉式

public class LHSingletonOne {

    private static LHSingletonOne instance = null;

    private LHSingletonOne(){
        System.out.println("我是懒汉使用时加载");
    }

    public static LHSingletonOne getInstance(){
        //同步锁,解决线程安全问题
        synchronized (LHSingletonOne.class){
            if (instance == null){
                instance = new LHSingletonOne();
            }
        }
        return instance;
    }

    public void doSomething(){
        System.out.println("懒汉起来干活了");
    }
}

双检锁的懒汉式

public class LHSingletonTwo {

    private static LHSingletonTwo instance = null;

    private LHSingletonTwo(){
        System.out.println("双检锁懒汉式效率高");
    }

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

    public void doSomething(){
        System.out.println("双检锁懒汉式效率高");
    }
}

懒汉式的特点

  • 线程安全
  • 第一次使用时,才创建单例实例-懒加载
  • 使用同步锁和检查对象是否被创建,效率比较慢
  • 双检锁只有第一次getInstance被调用时,需要使用同步锁;单检锁每次调用getInstance都会使用同步锁。所以双检锁懒汉式比单检锁懒汉式效率更高

5. 使用静态内部类来实现单例

如果你想要实现延迟加载,又不想让同步锁影响程序的性能,可以使用静态内部类来显现。
public class StaticSingleton {

    private StaticSingleton(){
        System.out.println("StaticSingleton is create");
    }
	//静态内部类实现延迟加载
    private static class SingletonHolder{
        private static StaticSingleton instance = new StaticSingleton();
    }

    public static StaticSingleton getInstance(){
        return SingletonHolder.instance;
    }

    public void doSomething(){
        System.out.println("doSomething……");
    }
}
  • 延迟加载
  • 没有同步锁
  • 没有线程安全问题
  • 当StaticSingleton被加载时,其内部类并不会被初始化;而当 getInstance方法被调用时才会加载SingletonHolder,从而初始化instance。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值