设计模式——单例模式

单例模式概述

确保一个类只有一个实例,并且提供一个全局访问点来访问这个唯一实例。
对于一个软件系统要求只有一个实例类,例如:任务管理器。

单例模式的结构与实现

提出问题

抛出问题:如何保证一个类只有一个实例呢?

引导大家思考

一个类,既然只能由一个实例,就表明他在client(客户端类)里面不能调用这个类的构造方法。不然client类可以无数次创建这个类的对象。
那么,我们就顺利成章的想到将这个构造方法私有化——定义为private。

单例模式最重要的思想就是:构造器私有。只有这样才能确保用户无法通过new关键字实例化它。(但是通过反射可以创建实例,后面会写)。
在这里插入图片描述

继续思考:既然构造器私有,那么外面就不能创造这个对象。这样这个对象就只能写在类里面。那么问题来了这个成员变量是用public还是private(大家可以参考这篇文章http://t.csdn.cn/SoeMF

既然这样我们就会用一个private修饰这个成员对象
在这里插入图片描述

继续思考:这个时候我们还差一个方法来访问
要想访问一个类中的方法有两个方式

  1. new一个对应类的对象,通过对象.方法名()来调用我们的尘缘方法
  2. 类名.方法名来调用成员方法

因为构造器私有了,所以我们自然而然只能使用第二种方向,所以我们返回单例对象的方法必须用static修饰。

这样我们的方法就是一个静态类方法,而静态类方法又不能调用非静态类的成员变量,所以我们的成员变量最后就是private static去修饰

代码具体实现

最终展示类的定义

在这里插入图片描述

单例模式的实现方法

单例模式能确保一个类只有一个实例,而且它能自行实例化并向整个系统提供这个实例。在计算机系统中的连接池、缓存、日志、打印机等管理类通常被设计成单例类。一家企业只有一位首席执行官(CEO),请使用懒汉式定义单例类CEO,属性自行定义,对象ceo的方法是主持董事会议holdAMeeting()。

public class CEO {
    private volatile static CEO chairman;
    private CEO(){
    }
    public static CEO getBean() {
        if (chairman == null) {
            synchronized (CEO.class) {
                if (chairman == null) {
                    chairman = new CEO();
                }
            }
        }
        return chairman;
    }
    public void holdAMeeting(){
        System.out.println("hold a meeting");
    }
    public static void main(String[] args) {
        CEO chairman=CEO.getBean();
        chairman.holdAMeeting();
    }
}

1.饿汉式单例(不推荐)

在定义静态变量的时候实例化单例类,即类加载时单例对象就已创建。
在这里插入图片描述

缺点

由于饿汉式单例,在使用之前就申请好内存空间了,但有可能用不到,占用资源。所以提出懒汉式单例

2.懒汉式单例(不推荐)

要用的时候给它实例化
在这里插入图片描述

提出问题

这个代码单线程下是没有问题的,但是多线程就会出问题

这里我举个例子,大家就可以明白为什么多线程下会出问题了。

public class SingletonPattern {
    private static SingletonPattern instance;
    private SingletonPattern(){
        System.out.println(Thread.currentThread().getName()+" is instantiated");
    }
    private static SingletonPattern getInstance(){
        if(instance==null){
            instance=new SingletonPattern();
        }
        return instance;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
             new Thread(()-> SingletonPattern.getInstance()).start();
        }
    }
}

结果截图
在这里插入图片描述

结果可以发现,在抢占cpu时,有多个线程发现instance==null,所以都调用了构造方法。

这个时候我们就需要加锁synchronized,这个时候就提出了我们的双重检查锁定(Double-Check Locking)

3.(DCL)双重检查锁定(推荐)

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

提出问题

但是这个代码还是有问题
大家知道多线程高并发有三要素

  1. 原子性:指的是⼀个或者多个操作,要么全部执⾏并且在执⾏的过程中不被其他操作打断,要么就全部不执⾏。原⼦性是数据⼀致性的保障。
  2. 有序性:程序的执⾏顺序按照代码的先后顺序来执⾏。单线程简单的事,多线程并发就不容易保障了。
  3. 可见性:指多个线程操作⼀个共享变量时,其中⼀个线程对变量进⾏修改后,其他线程可以⽴即看到修改的结果。(线程间的通信实现)

显然可见这段代码不满足原子性和有序性。

instance=new SingletonPattern();

创建对象的这个操作分三步

  1. 在heap(堆)中先申请一个内存空间
  2. new 执行构造方法
  3. 把实例化的对象与内存空间关联。

有可能是132或者123的顺序。所以这里就引出了修饰符volatile

volatile修饰符

在这里插入图片描述

继续提出问题

但是通过反射依旧可以破坏它,具体看代码

import java.lang.reflect.Constructor;
public class SingletonPattern {
    private volatile static SingletonPattern instance;
    private SingletonPattern(){
    }
    private static SingletonPattern getInstance(){
        if(instance==null){
            synchronized (SingletonPattern.class){
                if(instance==null){
                    instance=new SingletonPattern();
                }
            }
        }
        return instance;
    }
    public static void main(String[] args) throws Exception {
        SingletonPattern instance=SingletonPattern.getInstance();
        Constructor<SingletonPattern> declaredConstructor = SingletonPattern.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        SingletonPattern instance2=declaredConstructor.newInstance();
        System.out.println(instance2.hashCode());
        System.out.println(instance.hashCode());
    }
}

在这里插入图片描述

对无参构造器下文章

反射其实走的就是Singleton的私有无参构造器,我们在无参构造器里也实现类锁,只要instance被实例化了,下一次就没法再走无参构造器

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

在这里插入图片描述

继续提出问题

其实两个对象都用反射获得,依旧能破坏。解决思想是:设置一个成员变量开关,代码大家可以自己去写一写。评论区给大家回复

4.静态内部类实现单例模式

这里我就不介绍了,挺简单的

5.枚举

枚举是实现单例模式最安全的方法

public enum enumSingleton {
    ENUM_SINGLETON;
    public enumSingleton getEnumSingleton(){
        return ENUM_SINGLETON;
    }
}

缺点

枚举类确实能够防止序列化和多线程的问题,但同时它因为它继承了lang.Enum,是一个final class,所以不允许其他类继承它。

单例模式应用实例(作业)

单例模式能确保一个类只有一个实例,而且它能自行实例化并向整个系统提供这个实例。在计算机系统中的连接池、缓存、日志、打印机等管理类通常被设计成单例类。一家企业只有一位首席执行官(CEO),请使用懒汉式定义单例类CEO,属性自行定义,对象ceo的方法是主持董事会议holdAMeeting()。

public class CEO {
    private volatile static CEO chairman;
    private CEO(){
    }
    public static CEO getBean() {
        if (chairman == null) {
            synchronized (CEO.class) {
                if (chairman == null) {
                    chairman = new CEO();
                }
            }
        }
        return chairman;
    }
    public void holdAMeeting(){
        System.out.println("hold a meeting");
    }
    public static void main(String[] args) {
        CEO chairman=CEO.getBean();
        chairman.holdAMeeting();
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值