23种设计模式——单例模式(所属创建型模式)

23种设计模式——单例模式(所属创建型模式)

本章节的源代码位于gitee上,想要下载的请点击单例设计模式

简单聊聊单例设计模式

聊到一种设计模式,首先我们需要知道它是干什么的,有什么用。首先来看看正常我们在进行实例化对象时的操作。

class Message{
    public Message(){
        System.out.println("构造方法");
    }
    public void printf(){
        System.out.println("C3H2");
    }
}
public class SingletonDesignDemo {
    public static void main(String[] args) {
        Message messageA = new Message();
        Message messageB = new Message();
        messageA.printf();
        messageB.printf();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qwkE8JjO-1602297840783)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201010094149959.png)]

从这里我们可以看出我们已经创建了两个实例出来。那么单例模式是什么呢?

单例模式主要是控制实例化产生个数的设计操作。因为有些业务的需求(构建数据库连接)的时候,只允许一个提供一个实例化对象。那么这个时候我们就需要控制构造方法了,因为所有对象的实例化都需要调用构造方法,而如果没有构造方法,那么就无法从外部随意调用并进行实例化操作了。

单例设计模式分为两种,一种是懒汉式,一种为饿汉式。下面就这两种来展开说明。

懒汉式单例设计模式

懒汉式可以直接从字面上理解,就是非常懒,干什么都需要别人催促。就好像你家人在过节的时候。如果没人来,你家就不准备东西,别人来了才准备。

表现在代码上就是如下:

class Message2{
    private Message2(){
        System.out.println("构造方法");
    }
    private static Message2 message2 = null;
    public static Message2 getInstance(){
        if(message2==null){
            message2 = new Message2();
        }
        return message2;
    }
    public void printf(){
        System.out.println("C3H2");
    }
}
public class LazySingleton {
    public static void main(String[] args) {
        Message1 messageA = Message1.getInstance();
        Message1 messageB = Message1.getInstance();
        messageA.printf();
        messageB.printf();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4BQ9Pvrp-1602297840791)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201010100024126.png)]

我们在使用单例的时候明显不同的是讲构造方法进行了私有化,一旦构造方法被私有化了之后,就不能在外部通过new来创建对象了。但是我们又需要将类实例化,于是我们提供了一个方法getInstance()该方法就是用于于外部进行交互。因为没有被实例化就要被调用,所以该类要被static进行修饰。这样就能保证所创建的对象只有一个了。

饿汉式单例设计模式

饿汉式相比于懒汉式有所不同,但是都大同小异。懒汉式是什么都等着别人上门了才处理。饿汉式则是我早早的就将东西准备好,就等你上门来。具体的表现形式如下:

class Message1{
    private Message1(){
        System.out.println("构造方法");
    }
    private static Message1 message1 = new Message1();
    public static Message1 getInstance(){
        return message1;
    }
    public void printf(){
        System.out.println("C3H2");
    }
}
public class EagerSingleton {
    public static void main(String[] args) {
        Message1 messageA = Message1.getInstance();
        Message1 messageB = Message1.getInstance();
        messageA.printf();
        messageB.printf();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Il89vno-1602297840795)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201010095933376.png)]

多线程对单例模式的影响

我们说一个程序开发出来之后不可能只有一个人使用,如果是这样那它的意义就不是很大了。因此无论干什么都需要考虑到多线程在并发时对程序的影响。那么单例模式会不会在多线程的冲击之下出现问题呢?多说无益,直接上代码。

class Message3{
    private Message3(){
        System.out.println("构造方法");
    }
    private static Message3 message3 = null;
    public static Message3 getInstance(){
        if(message3==null){
            message3 = new Message3();
        }
        return message3;
    }
    public void printf(){
        System.out.println("C3H2");
    }
}
public class ThreadSingleton {
    public static void main(String[] args) {
        for(int i = 0 ; i < 5 ; i ++){
            new Thread(()->{
                Message3 message3 = Message3.getInstance();
                message3.printf();
            }).start();
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XdTkVhjK-1602297840800)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201010101206516.png)]

这里使用的是懒汉式来进行的测试,可以看出在开辟了五个线程之后出现了五次调用构造方法,也就是说进行了五次实例化。因此多线程对懒汉式是有影响的,但是对饿汉式是否有影响呢?各位可以自己去测测。

如果这个问题不解决的话,那这种单例模式和不要有什么区别呢?于是我们想到了同步synchronized,使用同步是否能解决问题呢?

public static synchronized Message3 getInstance(){
    if(message3==null){
        message3 = new Message3();
    }
    return message3;
}

直接在这个getInstance方法上添加了同步的操作。是否可以完成呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kX5asU0i-1602297840804)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201010101629828.png)]

事实证明这是可以的,但是我们这样将同步添加到方法上,不会影响其执行速率吗?我们就不可以直接对判断处进行加锁操作吗?

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

我们这样就完成了对需要判断的语句进行加锁。这两种锁有什么区别呢?等后续有机会在细聊吧。只能说一个锁的是方法,一个是类,这不同的锁。想要详细了解锁,需要好好理解八锁问题。

反射对单例设计的影响

上面我们使用synchronized解决了多线程对单例模式带来的影响。那么反射又会有什么影响呢?或者是有没有影响呢?

我们说反射是Java中一个非常重要的特征,可以说:没有反射,就没有Java现在的地位。那么这个反射有什么样的操作呢?

我个人认为反射最大的问题,就是它那个可以破解封装类的修饰权限的功能。这个功能可以让Java的封装直接崩溃。那么反映到单例上又是怎样的呢?

class Message4{
    private Message4(){
        System.out.println("构造方法");
    }
    private static Message4 message4 = null;
    public static Message4 getInstance(){
        if(message4==null){
            message4 = new Message4();
        }
        return message4;
    }
    public void printf(){
        System.out.println("C3H2");
    }
}
public class ReflectSingleton {
    public static void main(String[] args) throws Exception {
        Message4 messageA = Message4.getInstance();
        Class<?> clazz = Message4.class;
        Constructor con = clazz.getDeclaredConstructor(null);
        con.setAccessible(true);
        Object object = con.newInstance();
        Field messageField = clazz.getDeclaredField("message4");
        messageField.setAccessible(true);
        messageField.set(object,null);
        Message4 messageB = Message4.getInstance();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EgfSjSZM-1602297840808)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201010103836243.png)]

通过构造方法不仅可以直接调用构造方法,而且还可以修改其属性的值。

反射导致的问题是无法解决的,除法你使用了枚举类。那么枚举为什么能解决这个问题呢?请期待后续分享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值