设计模式之7种单例模式

单例模式

设计模式类型

设计模式分为三种类型,共23

  1. 创建型模式单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
  2. 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
  3. 行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)。
基本介绍:

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于 创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

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

何时使用:

当您想控制实例数目,节省系统资源的时候

比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session
对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个
SessionFactory就够,这是就会使用到单例模式。

单例模式的8种实现方式:
方式 是否推荐
1 饿汉式(静态常量) 可用
2 饿汉式(静态代码块) 可用
3 懒汉式(线程不安全) 禁止
4 懒汉式(线程安全,同步方法) 不推荐
5 懒汉式(线程安全,同步代码块) 错误
6 双重检查 推荐
7 静态内部类 推荐
8 枚举 推荐

1.饿汉式(静态常量)

特点
是否 Lazy 初始化
是否多线程安全
实现难度

描述:

  1. 构造器私有化 (防止 new )
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法。getInstance
  4. 代码实现

代码实现:

package 设计模式.单例模式;

/**
 * 静态常亮
 *
 * @author 孙一鸣 on 2020/2/4
 */
public class 饿汉式1 {
    public static void main(String[] args) {
        Singleton instance=Singleton.getSingleton();
        Singleton instance2=Singleton.getSingleton();

        System.out.println(instance == instance);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());

    }
}
class  Singleton{
    //1.构造器私有化
    private Singleton() {
    }

    //2.类内部创建对象实例
    //static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问。
    private final static  Singleton SINGLETON =  new Singleton();


    //3.提供一个共有的静态方法,返回实例
    public  static Singleton getSingleton(){
        return SINGLETON;
    }
}

优缺点说明:

  1. 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同
    步问题。
  2. 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始
    至终从未使用过这个实例,则会造成内存的浪费
  3. 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载
    时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载
    的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类
    装载,这时候初始化instance就没有达到lazy loading的效果
  4. 结论:这种单例模式可用,可能造成内存浪费

代码分析:
本次饿汉式代码实现围绕着静态常亮四字来展开,说到静态常亮就得说说关键字
static 和 final
static:关键字可以用来修饰代码块表示静态代码块修饰成员变量表示全局静态成员变量,修饰方法表示静态方法。

静态是相对于动态的,动态是指Java程序在JVM上运行时,JVM会根据程序的需要动态创建对象并存储对象(分配内存),对象使命结束后,对象会被垃圾回收器销毁,即内存回收由JVM统一管理并分配给其他新创建的对象;静态是指Java程序还没有运行时,JVM就会为加载的类分配空间存储,被static关键字修饰的内容;如静态成员变量,Java类加载到JVM中,JVM会把类以及类的静态成员变量存储在方法区,我们知道方法区是线程共享且很少发生GC的区域,所以被static关键字修饰的内容都是全局共享的,且只会为其分配一次存储空间。
设计模式之7种单例模式_第1张图片

实际而言,方法区(Method Area)和堆一样,是各个线程共享的内存区域,它用于存储虚拟机加载的:类信息+普通常量+静态常量+编译器编译后的代码等等,虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
对于HotSpot虚拟机,很多开发者习惯将方法区称之为“永久代(Parmanent Gen)” ,但严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于是一个接口interface)的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串常量池移走。
JDK1.8以后 。永久代被移除,转换为元空间。

final: final修饰的属性表明是一个常数(创建后不能被修改)

2.饿汉式(静态代码块)

特点
是否 Lazy 初始化
是否多线程安全
实现难度

描述:

  1. 构造器私有化 (防止 new )
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法。getInstance
  4. 代码实现

代码实现:

package 设计模式.单例模式;

/**
 * 静态代码块
 *
 * @author 孙一鸣 on 2020/2/4
 */
public class 饿汉式2 {
    public static void main(String[] args) {
        Singleton2 instance=Singleton2.getSingleton();
        Singleton2 instance2=Singleton2.getSingleton();


        System.out.println(instance == instance);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());

    }
}
class  Singleton2{
    //1.构造器私有化
    private Singleton2() {
    }

    //2.类内部创建对象实例
    //static修饰的属性强调它们只有一个,static 可以通过类名访问。
    private  static  Singleton2 SINGLETON ;

    static {
        SINGLETON = new Singleton2();
    }

    //3.提供一个共有的静态方法,返回实例
    public  static Singleton2 getSingleton(){
        return SINGLETON;
    }
}

优缺点说明:

  1. 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
  2. 结论:这种单例模式可用,但是可能造成内存浪费

3.懒汉式(线程不安全)

特点
是否 Lazy 初始化
是否多线程安全
实现难度

代码实现:

package 设计模式.单例模式;

/**
 * 线程不安全
 *
 * @author 孙一鸣 on 2020/2/4
 */
public class 懒汉式3 {
    public static void main(String[] args) {
        Singleton3 instance=Singleton3.getSingleton();
        Singleton3 instance2=Singleton3.getSingleton();

        System.out.println(instance == instance);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }
}
class Singleton3{
    private static Singleton3 instance;

    private  Singleton3(){};

    //提供一个静态的公有方法,使用到该方法时才去创建对象
    public  static Singleton3 getSingleton(){
        if (instance == null){
            instance = new Singleton3();
        }
        return instance;
    }
}

优缺点说明:

  1. 起到了Lazy Loading的效果,但是只能在单线程下使用。
  2. 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及
    往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以
    在多线程环境下不可使用这种方式
  3. 结论:在实际开发中,不要使用这种方式

4.懒汉式(线程安全,同步方法)

特点
是否 Lazy 初始化
是否多线程安全
实现难度

代码实现:

package 设计模式.单例模式;

/**
 * 线程安全
 *
 * @author 孙一鸣 on 2020/2/4
 */
public class 懒汉式4 {
    public static void main(String[] args) {
        Singleton4 instance=Singleton4.getSingleton();
        Singleton4 instance2=Singleton4.getSingleton();

        System.out.println(instance == instance);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());

    }
}
class Singleton4{
    private static Singleton4 instance;

    private  Singleton4(){};

    //提供一个静态的公有方法,使用到该方法时才去创建对象
    public synchronized static Singleton4 getSingleton(){
        if (instance == null){
            instance = new Singleton4();
        }
        return instance;
    }
}

优缺点说明:

  1. 解决了线程不安全问题
  2. 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行
    同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,
    直接return就行了。方法进行同步效率太低
  3. 结论:在实际开发中,不推荐使用这种方式

5. 懒汉式(线程安全,同步代码块)–错误示例

代码实现:

package 设计模式.单例模式;

/**
 * 同步代码块 错误案例
 *
 * @author 孙一鸣 on 2020/2/4
 */
public class 饿汉式5 {
    public static void main(String[] args) {
        Singleton5 instance=Singleton5.getSingleton();
        Singleton5 instance2=Singleton5.getSingleton();

        System.out.println(instance == instance);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());

    }
}
class Singleton5{
    private static Singleton5 instance;

    private  Singleton5(){};

    //提供一个静态的公有方法,使用到该方法时才去创建对象
    public  static Singleton5 getSingleton(){

        if (instance == null){
            synchronized (Singleton.class){
                instance = new Singleton5();
            }

        }
        return instance;
    }
}

缺点说明:

  1. 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,
    改为同步产生实例化的的代码块
  2. 但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
  3. 结论:在实际开发中,不能使用这种方式

6 .双重检查

特点
是否 Lazy 初始化
是否多线程安全
实现难度

代码实现:

package 设计模式.单例模式;

/**
 * 
 *
 * @author 孙一鸣 on 2020/2/4
 */
public class 双重检查 {
    public static void main(String[] args) {
        Singleton6 instance=Singleton6.getSingleton();
        Singleton6 instance2=Singleton6.getSingleton();

        System.out.println(instance == instance);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());

    }
}

class Singleton6{
    private static volatile Singleton6 instance;

    private  Singleton6(){};

    //提供一个静态的公有方法,使用到该方法时才去创建对象
    public  static Singleton6 getSingleton(){

        if (instance == null){
            synchronized (Singleton.class){
                if (instance == null){
                    instance = new Singleton6();
                }
            }

        }
        return instance;
    }
}

优缺点说明:

  1. Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两
    次if (singleton==null)检查,这样就可以保证线程安全了。
  2. 这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),
    直接return实例化对象,也避免的反复进行方法同步.
  3. 线程安全;延迟加载;效率较高
  4. 结论:在实际开发中,推荐使用这种单例设计模式

代码分析:
双重检查使用了多线程中常用关键字 volatile
1.volatile是JAVa虚拟机提供的轻量级的同步机制,有三大特点:
1.1保证可见性 1.2 不保证原子性 1.3禁止指令重排
设计模式之7种单例模式_第2张图片设计模式之7种单例模式_第3张图片

7.静态内部类

特点
是否 Lazy 初始化
是否多线程安全
实现难度 一般

代码实现:

package 设计模式.单例模式;

/**
 * @author 孙一鸣 on 2020/2/4
 */
public class 静态内部类7 {
    public static void main(String[] args) {
        Singleton8 instance=Singleton8.getInstance();
        Singleton8 instance2=Singleton8.getInstance();

        System.out.println(instance == instance);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode="+ instance2.hashCode());

    }
}
class Singleton8{
    //构造器私有化
    private Singleton8(){};
    
    //写一个静态内部类,该类有静态属性Singleton
    private  static class  SingletonInstance{
        private static final Singleton8 SINGLETON_8= new Singleton8();
    }
    
    
    //提供静态公有方法
    public static  synchronized  Singleton8 getInstance(){
        return SingletonInstance.SINGLETON_8;
    }
}

优缺点说明:
  1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
  2. 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化
    时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的
    实例化。
  3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们
    保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
  4. 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
  5. 结论:推荐使用.

代码分析:

静态内部类: 使用它的时候才会加载
静态变量,静态方法,静态块等都是类级别的属性,而不是单纯的对象属性。他们在类第一次被使用时被加载(记住,是一次使用,不一定是实例化)。我们可以简单得用 类名.变量 或者 类名.方法来调用它们。与调用没有被static 修饰过变量和方法不同的是:一般变量和方法是用当前对象的引用(即this)来调用的,静态的方法和变量则不需要。从一个角度上来说,它们是共享给所有对象的,不是一个角度私有。这点上,静态内部类也是一样的。

静态内部类的加载过程:
静态内部类的加载不需要依附外部类,在使用时才加载。不过在加载静态内部类的过程中也会加载外部类。以上花了很多功夫来说明了

8.枚举

特点
是否 Lazy 初始化
是否多线程安全
实现难度

代码实现:

package 设计模式.单例模式;

/**
 * @author 孙一鸣 on 2020/2/4
 */
public class 枚举8 {
    public static void main(String[] args) {
        Singleton9 instance=Singleton9.INSTACNCE;
        Singleton9 instance2=Singleton9.INSTACNCE;

        System.out.println(instance == instance);
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode="+ instance2.hashCode());
       instance.sayOK();

    }
}
enum Singleton9{
    INSTACNCE;
    public void sayOK(){
        System.out.println("ok~");
    }
}

分析:

描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。

你可能感兴趣的:(Java设计模式详细学习专栏)