《HF 设计模式》 C5 单例模式

1 管理公共资源的对象只需要一个

1.1 有时只需要一个对象

每一个类都可以通过new来生成若干个对象,但是有些对象我们只需要一个,如:线程池,注册表,日志等对象,这些对象只能有一个,如果制造出多个对象,就会导致程序的行为异常,资源使用过量,导致不一样的结果等

像某个普通的类,如Person,可以随便的生成Person对象,因为Person类的初衷就是为了生成大量Person对象,而类似于注册表这种对象,是为了管理公共资源的,多个注册表会导致混乱,这种对象应该有且仅有一个

1.2 单例模式

单例模式:

确保一个类只有一个实例,并提供一个全局访问点

使用单例模式创建唯一的对象,可以这样做:

/**
 * @author 雫
 * @date 2021/3/4 - 11:57
 * @function 注册表
 * 该类只提供一个对象
 */
public class Registry {
    
    
    private static Registry instance;

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

在上述的程序中,我们将构造器改为private类型,因此该类的对象不能在外部被创建,但提供一个静态方法getInstance()来让用户取得唯一的注册表对象,如果是第一次调用getInstance(),就会调用构造方法生成一个Registry对象,之后的每次调用都返回第一次创建好了的Registry对象

这里生成唯一对象的方式,采用了懒汉模式,即需要时再创建,这对资源敏感的对象特别重要

如果需要注册表对象,利用单例模式就可以保证只有一个,这样的全局资源只有一份,单例模式常常被用来管理共享的资源,如数据库连接池或线程池等

1.3 模拟巧克力锅炉

每个巧克力工厂都有一个锅炉,用来将巧克力和牛奶融合到一起,来看一个锅炉的模拟类:

/**
 * @author 雫
 * @date 2021/3/4 - 12:15
 * @function 巧克力锅炉
 */
public class ChocolateBoiler {
    
    
    private boolean empty;
    private boolean boiled;

    public ChocolateBoiler() {
    
    
        this.empty = true;
        this.boiled = false;
    }

    public boolean isEmpty() {
    
    
        return empty;
    }

    public boolean isBoiled() {
    
    
        return boiled;
    }

    //向锅炉中导入原料,倒入前需要保证锅炉是空的
    //倒入后令锅炉不为空,煮沸标志为false
    public void fill() {
    
    
        if(isEmpty()) {
    
    
            empty = false;
            boiled = false;
        }
    }

    //从锅炉中倒出原料,倒出前需要保证锅炉已满且已煮沸
    //倒出后,令锅炉为空
    public void drain() {
    
    
        if(!isEmpty() && isBoiled()) {
    
    
            empty = true;
        }
    }

    //煮沸锅炉中的原料,开煮前需要保证锅炉不为空,且还未沸腾
    public void boil() {
    
    
        if(!isEmpty() && !isBoiled()) {
    
    
            boiled = true;
        }
    }
    
}

这是一个简单的模拟类,但是现在有一个问题:
如果不小心生成了两个或多个ChocolateBoiler对象,会发生什么?

1,锅炉对象现实中只有一个,应该禁止生成多个
   公共资源应该只能有且只有一个
   
2,生成多个对象会导致程序混乱
   如cb1和cb2,调用cb1的fill()后,调用了cb2的boil()
   又去调用了cb1的drain()

用单例模式改造ChocolateBoiler:

/**
 * @author 雫
 * @date 2021/3/4 - 12:29
 * @function 单例巧克力锅炉
 */
public class SingletonChocolateBoiler {
    
    
    private static SingletonChocolateBoiler chocolateBoiler;

    private boolean empty;
    private boolean boiled;

    private SingletonChocolateBoiler() {
    
    
        this.empty = true;
        this.boiled = false;
    }

    public boolean isEmpty() {
    
    
        return empty;
    }

    public boolean isBoiled() {
    
    
        return boiled;
    }

    public void setEmpty(boolean empty) {
    
    
        this.empty = empty;
    }

    public void setBoiled(boolean boiled) {
    
    
        this.boiled = boiled;
    }


    public static SingletonChocolateBoiler getInstance() {
    
    
        if(chocolateBoiler == null) {
    
    
            chocolateBoiler = new SingletonChocolateBoiler();
        }
        return chocolateBoiler;
    }


}

其它方法...

测试:
在这里插入图片描述
通过单例模式保证了管理共享资源的对象只有一份

1.4 当多线程参与进来…

现在我们再使用多线程优化一下SingletonCholateBoiler,有两个线程,线程1和线程2,它们都会执行下面的代码:

SingletonChocolateBoiler c 
		= SingletonChocolateBoiler.getInstance();

c.fill();
c.boil();
c.drain();

如果是单线程环境,这个程序不会出现问题,但如果面对多线程:
在这里插入图片描述
原本只应该有一个的对象变成了两个,让接下来的工作变得混乱

为此我们需要将多线程环境考虑进去,有以下几种解决方法:

1,同步getInstance()方法
在这里插入图片描述
给getInstance()方法上锁,被static修饰的方法,被synchronized修饰后,其锁为类锁,两个线程竞争同一把锁,保证了方法的原子性

但是直接同步getInstance()方法一点也不好,只有第一次执行该方法时才需要同步,之后每次调用该方法,同步都是一种累赘,且同步一个方法可能造成程序执行效率下降100倍

如果类似getInstance()这样的方法被多次频繁地使用,就不建议使用synchronized直接同步方法,这会拖垮程序的性能

2,采用懒汉模式创建唯一对象

在这里插入图片描述

采用懒汉模式急切地创建对象,利用这个方法JVM保证任何线程在试图获取chocolateBoiler前,一定已经创建好了唯一对象

但是这样的方法,不适用于创建对象需要太大负担的类,如果系统中有许多懒汉模式创建对象的方法,而创建这些对象并不简单,那么系统的性能就会降低,且给用户不好的使用体验(打开系统,进行一系列对象创建,用户只能看着屏幕等待)

3,双重检查加锁

在这里插入图片描述
采用类锁保证多个线程竞争同一把锁,第一次生成对象时才需要同步,其它时候直接返回对象,这样 双重检查加锁 的方式可以摆脱同步方法和饿汉模式的缺点

1.5 单例模式小结

1,单例模式确保程序中一个类最多只有一个对象

2,实现单例模式需要私有的构造器,一个静态变量和一个静态方法

3,如果使用多个类加载器,可能导致单例失效而产生多个对象,即不同的类加载器可能会加载同一个类,以此让单例失效

4,使用锁来其它方式来确保多线程环境下,单例也能正常工作

猜你喜欢

转载自blog.csdn.net/weixin_43541094/article/details/114365018