单例设计模式


单例模式定义
保证一个类只有一个实例, 并且提供一个全局访问点

什么样的场景下需要使用单例?
重量级的对象, 不需要多个实例.
如线程池, 或者数据库的连接池. 我们希望我们直接从连接池里面拿连接就可以了, 而不是每次请求都去创建一个对应的连接池

单例模式的具体实现

懒汉模式

特点: 延迟加载, 只有在真正使用的时候才开始实例化.

代码:

package com.company.shejimoshi;

/**
 * @author chenxinshinian.com
 * @date 2020/4/19 19:38
 */
public class LazySingletonTest {
    public static void main(String[] args) {
        LazySingletion instance = LazySingletion.getInstance();
        LazySingletion instance1 = LazySingletion.getInstance();
        System.out.println(instance == instance1); // true
    }

static class LazySingletion{
    /**
     * 首先提供一个静态的私有属性
     */
    private static LazySingletion instance;

    /**
     * 提供一个私有的构造函数, 避免直接从外部对对应实例的创建
     */
    private LazySingletion(){

    }

    /**
     * 提供一个全局的访问点
     * @return
     */
    public static LazySingletion getInstance(){
        /**
         * 在调用getInstance的时候在对他进行实例化
         */
        if(instance == null){
            instance = new LazySingletion();
        }
        return instance;
    }
    }
}

单例模式在单线程的环境下这样是OK的, 下面试一下两个线程:

package com.company.shejimoshi;

/**
 * @author chenxinshinian.com
 * @date 2020/4/19 19:38
 */
public class LazySingletonTest {
    public static void main(String[] args) {

        new Thread( ()->{
            LazySingletion instance = LazySingletion.getInstance();
            System.out.println(instance);
        }).start();


        new Thread( ()->{
            LazySingletion instance = LazySingletion.getInstance();
            System.out.println(instance);
        }).start();
    }

static class LazySingletion{
    /**
     * 首先提供一个静态的私有属性
     */
    private static LazySingletion instance;

    /**
     * 提供一个私有的构造函数, 避免直接从外部对对应实例的创建
     */
    private LazySingletion(){

    }

    /**
     * 提供一个全局的访问点
     * @return
     */
    public static LazySingletion getInstance(){
        /**
         * 在调用getInstance的时候在对他进行实例化
         */
        if(instance == null){
            try {
                /**
                 * 避免一个线程提前完成, 让线程sleep 2000毫秒
                 */
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new LazySingletion();
        }
        return instance;
    }
    }
}

输出:

com.company.shejimoshi.LazySingletonTest$LazySingletion@3354d998
com.company.shejimoshi.LazySingletonTest$LazySingletion@7245297c

这个情况说明在多线程的情况下访问这个代码是会出现多线程问题的

两个线程同时都走到了这个代码块, 两个线程都会进行对应实例的创建, 这样就产生了两个对应的实例, 违背了单例模式的定义

解决方法:
给访问点加上synchronized

输出:

com.company.shejimoshi.LazySingletonTest$LazySingletion@632a10ea
com.company.shejimoshi.LazySingletonTest$LazySingletion@632a10ea

通过给getInstance加锁的方式确保了单例, 这样会有一定的性能损耗

优化方案

/**
     * 提供一个全局的访问点
     * @return
     */
    public static LazySingletion getInstance(){
        /**
         * 在调用getInstance的时候在对他进行实例化
         */
        if(instance == null){
            synchronized (LazySingletion.class){
                if(instance == null){
                    instance = new LazySingletion();
                }
            }
        }
        return instance;
    }

这样就能给锁进行一个很大的性能提升, 如果第一个线程就完成它的实例化的话, 那其他线程就不会在对他进行加锁了

饿汉模式

类加载的初始化阶段就完成了势力的初始化. 本质上就是借助于JVM类加载及政治, 保证实例的唯一性.

类加载过程:

  1. 加载二进制数到内存中, 生成对应的Class数据结构.
  2. 连接: a.验证 b.准备(给类的静态成员变量赋默认值), c.解析
  3. 初始化:给类的静态变量赋初始值

只有在真正使用对应的类时, 才会触发初始化 如(当前类时启动类即main函数所在类, 直接进行new操作, 访问静态属性 访问静态方法 用反射访问类, 初始化一个类的子类等.)

代码:

package com.company.shejimoshi;

/**
 * @author chenxinshinian.com
 * @date 2020/4/19 22:44
 */
public class HungrySingletonTest {
    public static void main(String[] args) {
        HungryStingleton instancel = HungryStingleton.getInstance();
        HungryStingleton instancel1 = HungryStingleton.getInstance();

        System.out.println(instancel == instancel1); // true



    }
}

/**
 * 单例模式-饿汉模式
 */
class HungryStingleton{
    private static HungryStingleton instance = new HungryStingleton();
    private HungryStingleton(){};

    public static HungryStingleton getInstance(){
        return instance;
    }

}

静态内部类

代码:

import com.sun.org.apache.bcel.internal.classfile.InnerClass;

/**
 * @author chenxinshinian.com
 * @date 2020/4/19 23:04
 */
public class InnerClassSingletonTest {
    public static void main(String[] args) {
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        InnerClassSingleton instance1 = InnerClassSingleton.getInstance();
        System.out.println(instance == instance1); // true

    }
}

/**
 * 静态内部类 本质上也是基于JVM的类加载机制来实现的 而且是一种懒加载的方式
 */
class InnerClassSingleton{
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }

    private InnerClassSingleton(){

    }

    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}
  1. 静态内部类本质上是利用类的加载机制来保证线程安全
  2. 只有在实际使用的时候, 才会触发类的初始化, 所以也是懒加载的一种形式

文章作者: 拾年
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 拾年 !
评论
 上一篇
数据库逻辑设计 数据库逻辑设计
数据库设计数据库逻辑设计数据库物理设计数据库维护和优化 逻辑设计是做什么的 将需求转化为数据库的逻辑模型 通过ER图的形式对逻辑模型进行展示 同所选用的具体的DBMS系统无关 所谓的逻辑设计就是根据需求分析之所了解到的应用中所需要
2020-05-09
下一篇 
关于本博客 关于本博客
本博客是基于hexo搭建的纯静态博客,采用matery主题,也可以选择其他的主题,如其他优秀的主题有next,使用markdown编写文章,再通过hexo博客框架编译成静态文件,我这里租用的是阿里云的服务器和域名,写完文章直接编译成
2020-04-03
  目录