设计模式之单例模式

设计模式:

1.创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
2.结构型模式
3.行为型模式
并发型模式和线程池模式

单例模式的定义:

确保这个类在内存中只会存在一个对象,而且自行实例化并向整个应用系统提供这个实例。

单例模式的应用场景:

1.需要频繁的进行创建和销毁的对象;
2.创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
3.工具类对象;
4.频繁访问数据库或文件的对象

实例化:

实例化是指在面向对象的编程中,把用类创建对象的过程称为实例化。是将一个抽象的概念类,具体到该类实物的过程。实例化过程中一般由类名 对象名 = new 类名(参数1,参数2…参数n)构成。
用一个例子来简单的说明实例化这个概念:
“人”是一个类。“张三”是人类的一个具体例子;在编程时也是同样的道理,你先自己定义一个“类”,当你需要用时,用“类”的定义来创造一个具体的例子。
用类的定义来创建一个实例,就叫做类的实例化。

基本实现思路

单例的实现主要是通过以下两个步骤:

1.将该类的构造方法定义为私有方法,这样其他处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
2.在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用。

  1. 饿汉式
    饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期 都存在。它的好处是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。它的缺点也很明显,即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。
    缺点:类加载时就初始化,浪费内存。
    优点:没有加锁,执行效率会提高。
public class Singleton {
    //用static修饰是为了在类加载的时候就创建实例
    private static Singleton instance = new Singleton();
    
    //私有的构造方法,收回了别人的调用权限,使用权归自己所有
    private Singleton() {
        
    }
    
    //static修饰符帮助别人可以通过类直接调用这个方法
    public static Singleton getInstance() {
        return instance;
    }
}

class TestSingleton {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        //打印的肯定是true,因为访问的是同一个实例
        System.out.println(singleton1 == singleton2);
    }
}

2.懒汉式
懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。如果某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建,这个时候使用懒汉模式就是一个不错的选择。但是这里的懒汉模式并没有考虑线程安全问题,在多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,因此需要加锁解决线程同步问题。
缺点:必须加锁 synchronized 才能保证线程安全,但加锁会影响效率。
优点:第一次调用才初始化,避免内存浪费。

public class Singleton {
    //用一个null值的变量来存放实例,因为懒,在类加载的时候还没去创建实例
    private static Singleton instance = null;
    
    //私有的构造方法
    private Singleton() {
        
    }
    
    //调用这个方法的时候首先看看是不是第一次调用,第一次调用肯定没有创建这个实例,是的话就创建这个实例
    //如果不是第一次的话,就证明就这个instance中有值了,就可以直接直接返回了
    public static Singleton getInstance() {
        if(instance == null)
            instance = new Singleton();
        return instance;
    }
}

class TestSingleton {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        //打印的肯定是true,因为访问的是同一个实例
        System.out.println(singleton1 == singleton2);
    }
}

延迟加载的思想:

单例模式的懒汉式的实现方式就体现了延迟加载的思想,就是说,一开始不要加载资源或者数据,等到马上要使用这个资源或者数据的时候,才去加载,所以也叫Lazy Load,这在实际开发中是一种很常见的思想,尽可能的节约资源。

缓存的思想:

懒汉式的单例模式也有这个缓存的思想在里面,如果某些资源是存储在外部的,但是我们又很经常的使用它,我们每次从外部中去找,然后使用,这个的工作量是很大的,缓存的思想就是将这些资源放在内存中每次操作的时候就直接在内存中找就是了,是一种典型的空间换时间的方案。

单例模式的优缺点:

1.时间和空间

懒汉式就是典型的时间换空间的思想,每次都要去判断,看看是否需要实例,饿汉式是典型的空间换时间,当类加载的时候就创建好实例了,不管你用不用,反正就放在那里了,调用的时候就不能访问了,直接返回给你。

2.线程安全

从线程安全的角度上来说,饿汉式是安全的,但是懒汉式是不安全的,打个比方说,现在有线程A和线程B同时去调用getInstance方法,就可能出现线程并发的情况,如果所示:

在这里插入图片描述
从上面就可以分析出这个懒汉式是线程不安全的,下面我们演示一个线程安全的懒汉式:

//调用这个方法的时候首先看看是不是第一次调用,第一次调用肯定没有创建这个实例,是的话就创建这个实例
    //如果不是第一次的话,就证明就这个instance中有值了,就可以直接直接返回了
    public static synchronized Singleton getInstance() {
        if(instance == null)
            instance = new Singleton();
        return instance;
    }

其实就是在方法的前面加上synchronized,这个就是同步的标记,这样一来,线程就安全了。

synchronized

synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。它包括两种用法:synchronized 方法和 synchronized 块。 对于本文,只需要知道这一关键字即可,如有兴趣,可查看相关资料。