彻底弄懂 单例设计模式

一、设计模式简介

设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。”套路”

二、单例设计模式

2.1 单列设计模式简介

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。

2.2 单例设计模式优点

由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

2.3 单例设计模式思路

  1. 如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private【private权限修饰符,可以修饰成员变量、成员方法、内部类。作用:只能在本类中使用privite修饰的成员变量、成员方法】,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。

  2. 因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。

2.4 单例(Singleton)设计模式-饿汉式

直接上代码:

/**单例(Singleton)设计模式-饿汉式
 * private权限修饰符,可以修饰成员变量、成员方法、内部类。作用:只能在本类中使用private修饰的成员变量、成员方法】
 */
public class Student {
    
    // 第一步:私有化构造器。【让外部无法通过new来创建对象】
    private Student() {
    }

    
    // 第二步:创建一个对象
    /*  1. 为什么怎么做?
           不怎么做的话,我们就没有对象呀!构造器已经被我们设置为私有的了,外部不能造对象了,那我们就只能在内部提供一个对象。
        2. 为什么设置为私有的?
            想一想,如果不设置为私有的。当有一个类继承这个类的时候,该子类就可以访问这个属性了,从而得到了父类对象。
            这与我们初衷相违背。
        3.为什么设置为静态的?
            看第三步中的回答,从第三步反推过来的。需要把这个属性设置为私有的。
    * */
    private static Student student = new Student();

    
    // 第三步:对外提供一个公共静态方法。【以便获得第二步中创建的对象】
    /*1. 为什么是公共的?
    *    肯定是公共的,不然如私有方法,外部怎么能够访问得了,从而怎么得到这个唯一的对象
    * 2. 为什么是静态方法?
    *     第一:如果不是静态方法,我们需要通过new对象来访问该方法,和我们初衷相违背。
    *     第二:静态方法,我们可以通过类名.方法名的方式进行访问。
    *     第三:静态方法里面只能访问静态属性、静态方法,从而反推出第二步骤中的属性需要加static关键字
    * */

    public static Student getStudent() {
        return student;
    }
}

测试:

public class TestStudent {
    public static void main(String[] args) {
       // 1. 我们先来new一下,发现编译失败。【说明:外部通过构造器无法创建对象】
       // Student student = new Student();

        // 2. 比较创出的对像否是同一个对象,结果为true。【说明:证明是同一个对象】
        Student student1 = Student.getStudent();
        Student student2 = Student.getStudent();
        System.out.println(student1==student2);
    }
}

2.5 单例(Singleton)设计模式-懒汉式

错误写法:第三步有问题

/**
 * 单例(Singleton)设计模式-懒汉式
 */
public class Teacher {
    // 第一步:私有化构造器
    private Teacher() {

    }
    // 第二步:声明当前类对象,没有初始化
    private static Teacher teacher=null;

    // 第三步:提供公共静态方法,返回当前类的对象
    public static Teacher getTeacher() {
        Teacher teacher = new Teacher();
        return teacher;
    }
}

测试:这显然是两个对象

错误原因:调用一次方法,便会new一次,创建不同的对象。

正确写法:

/**
 * 单例(Singleton)设计模式-懒汉式
 */
public class Teacher {
    // 第一步:私有化构造器
    private Teacher() {

    }
    // 第二步:声明当前类对象,没有初始化
    private static Teacher teacher=null;

    // 第三步:提供公共静态方法,返回当前类的对象
    public static Teacher getTeacher() {
        if (teacher == null) {    // 先判断一下,如果该对象没有创建就new,return出去。如果已经有了就不创建了。
            Teacher teacher = new Teacher();
        }
        return teacher;
    }
}

2.6 饿汉式VS懒汉式

  1. 饿汉式

    ​ 坏处:对象加载时间过长【占用内存】

    ​ 好处:饿汉式是线程安全的

  2. 懒汉式

    ​ 好处:延迟对象的创建

    ​ 坏处:目前的写法,存在线程不安全。

    【线程不安全解释:假设有两个线程进入了getTeacher方法,线程一判断teacher会null时,此时CPU资源给到了线程二(线程一没有执行Teacher teacher = new Teacher()😉,线程二也判断teacher为null,执行Teacher teacher = new Teacher();语句。从而线程一、线程二得到的对象不是同一个对象】

2.7 同步机制解决懒汉式线程安全问题

方式一:同步方法

/**
 * 单例(Singleton)设计模式-懒汉式
 */
public class Teacher {
    // 第一步:私有化构造器
    private Teacher() {

    }
    // 第二步:声明当前类对象,没有初始化
    private static Teacher teacher=null;

    // 第三步:提供公共静态方法,返回当前类的对象
    public static synchronized Teacher getTeacher() { // 同步监视器(锁),该静态方法中为:Teacher.class
        if (teacher == null) {
            Teacher teacher = new Teacher();
        }
        return teacher;
    }
}

方式二:同步代码块

/**
 * 单例(Singleton)设计模式-懒汉式
 */
public class Teacher {
    // 第一步:私有化构造器
    private Teacher() {

    }
    // 第二步:声明当前类对象,没有初始化
    private static Teacher teacher=null;

    // 第三步:提供公共静态方法,返回当前类的对象
    public static Teacher getTeacher() {
        /*外面在加一层判断,效率较高。
        * 刚开始时比如有3个线程进来,此项teacher为null,都进到里面,3个线程排队。
        * 但是后面来的线程,就不用进同步代码块了【大家不用在同步代码块外面等待,而是判断teacher为true,直接拿走teacher对象即可】
        * */
        if (teacher == null) {	
            synchronized (Teacher.class) {
                if (teacher == null) {
                    Teacher teacher = new Teacher();
                }
            }
        }
        return teacher;
    }
}

2.8 总结

相对来说把线程安全解决后的懒汉式单例模式是优于饿汉式单例模式的。本篇知识点涉及到java的面向对象知识、关键字、修饰符、多线程等。

posted @ 2020-09-16 17:37  EternityNoah  阅读(288)  评论(3编辑  收藏