Java设计模式学习--单例模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_40646143/article/details/85013248

单例模式

  核心作用 保证一个类只有一个实例 , 并且提供一个访问该实习的全局访问点 . 

常见的应用场景 :

  • Windows 的 Tsask Manager (任务管理器) 就是典型的单例模式 当你打开任务管理器的时候,只要你能不关闭任务管理器页面,你电脑只能显示一个任务管理器页面 。
  • 项目中 , 读取配置文件的类 , 一般也只有一个对象。 没有必要每次使用配置文件数据,每次 new 一个对象去读取。
  • 数据库连接池的设计一般也是采用单例模式 , 因为数据库连接是一种数据库资源。
  •  在 Spring 中 , 每个 Bean 默认就是单例的 , 这样做的优点是 Spring 容器可以管理 。
  •  在 Spring MVC 框架爱/struts1 框架中 , 控制器对象也是单例的 。

单例模式有什么优点呢?

  1. 由于单例模式只生成一个实例,减少了系统性能开销 , 当一个对象的产生需要比较多的资源时 , 如 读取配置 , 产生其他依赖对象时 , 则可以通过在应用启动时直接产生一个单例对象, 然后永久驻留内存的放式解决 。
  2. 单例模式可以在系统设置全局的访问点 , 优化环共享资源访问 , 例如 可以设计一个单例类, 负责所有数据表的映射处理 。 

常见单例模式的四种实现放式

1), 饿汉式单例模式

/**
 * 饿汉式单例模式
 *
 * 当类初始化的时候,进行创建当前类的实例 线程安全  没有延迟加载
 */
public class SingletionDemo {

    //1 声明一个私有的静态变量
    private static SingletionDemo instance=new SingletionDemo();
    
    //2 私有化构造器 避免外部直接创建对象
    private SingletionDemo(){}
    
    //3 创建一个对外的公共的静态方法 访问该变量 
    //方法没有同步  效率高
    public static SingletionDemo getInstance() {
        return instance;
    }
}

饿汉式单例模式总结如下:

  •  在代码上, static 变量会在类装载时初始化 , 此时也不会涉及多个线程访问该对象的问题 。虚拟机保证只会装载一次该类 , 肯定不会发生并发访问的问题 。 因此, 可以省略Synchronized 关键字 。
  • 也存在这样一个问题: 如果只是加载本类 , 而不是调用getInstance() , 甚至永远没有调用 ,则会造成资源浪费 !

2),懒汉式单例模式

/** 懒汉式 单例模式
 * @author 晓电脑
 *
 * 单线程状态下是只是一个实例对象 如果多个线程访问则会出现问题
 * 多线程则会是多个对象  需要加上synchronized
 */
public class SingletionDemo01 {

    //1 声明一个私有的静态变量  
    private static SingletionDemo01 instance;

    //2 私有构造器 避免外部直接创建对象
    private SingletionDemo01(){}

    //3 创建一个对外的公共的静态方法  访问该变量 如果变量没有对象 创建对象
    public static SingletionDemo01 getInstance() throws InterruptedException {
        if (null == instance){
            instance=new SingletionDemo01();
        }
        return instance;

    }
}

如果我们使用main方法进行测试如下

 public static void main (String[] args) throws InterruptedException {
        System.out.println("_________单线程状态下_________");
        System.out.println(SingletionDemo01.getInstance());
        System.out.println(SingletionDemo01.getInstance());
    }

main是一个单线程, 所以 上面打印的俩个地址是一样的 , 如果模拟多线程的情况下,则会出现问题 ,我们改动懒汉单例模式如下

/** 懒汉式 单例模式
 * @author 晓电脑
 *
 * 单线程状态下是只是一个实例对象 如果多个线程访问则会出现问题
 * 多线程则会是多个对象  需要加上synchronized
 */
public class SingletionDemo01 {

    //1 声明一个私有的静态变量  
    private static SingletionDemo01 instance;

    //2 私有构造器 避免外部直接创建对象
    private SingletionDemo01(){}

    //3 创建一个对外的公共的静态方法  访问该变量 如果变量没有对象 创建对象 
    //需要在static 后面加上synchronized 
    public static SingletionDemo01 getInstance(Long time) throws InterruptedException {
        if (null == instance){
             // 加入延迟时间 time
            Thread.sleep(time);
            instance=new SingletionDemo01();
        }
        return instance;

    }
}

接下来创建一个线程,对懒汉单例模式进行访问

    /**
     * 创建一个线程 来执行懒汉单例模式
     */
    static class ThreadDemo extends Thread{
        private Long time;
        
        //构造器对延迟时间进行初始化
        public ThreadDemo(Long time){
            this.time=time;
        }
        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + "---->" + SingletionDemo01.getInstance(time));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

main方法进行测试多线程情况下访问懒汉单例模式

    public static void main (String[] args) throws ClassNotFoundException, InterruptedException {
        System.out.println("_________单线程状态下_________");
//        System.out.println(SingletionDemo01.getInstance());
//        System.out.println(SingletionDemo01.getInstance());
        System.out.println("______模拟多线程_______");
        //设置延迟时间
        ThreadDemo demo = new ThreadDemo(100L);
        //设置线程名称
        demo.setName("zs");
        //启动线程
        demo.start();

        ThreadDemo demo1 = new ThreadDemo(300L);
        demo1.setName("www");
        demo1.start();
        
    }

运行结果如下

     

总结 :

  • 懒汉单例模式,需要加上 Synchronized 同步锁 ,才能再多线程的情况下也是同一个对象,否则只是单线程下是同一个对象而多线程情况下,是不同的对象 。
  • Lazy load ! 延迟加载, 懒加载 ! 真正用的时候才加载 !
  • 也有一个问题: 资源利用率高了 。 但是 , 每次调用getInstance()方法都要同步,并发效率较低 。

3),静态内部类实现放式(也是一种懒加载放式)

/**静态内部类实现方式
 * @author 晓电脑
 */
public class StaticSingletion {
    //1 私有化构造器 避免外部类直接创建对象
    private StaticSingletion(){
    }

    //2 创建static 内部类
    private static class DemoStatic{
        private static final StaticSingletion instance=new StaticSingletion();
    }
    //3 提供外部访问的入口
    public static StaticSingletion getInstance(){
        return DemoStatic.instance;
    }
}

总结 :

  • 外部类没有 static 属性 ,则不会像饿汉式那样立即加载对象 。
  • 只有真正调用了 getInstance() 方法,才会加载静态内部类 。加载类时是线程安全的 。 instance 是static final 类型 ,保证了内存中只有这样的一个实例存在 ,而且 只能被赋值一次 ,从而保证了线程安全性 。
  • 兼备了并发高效调用和延迟加载的优势 。

4),使用枚举的方式实现单例模式

package com.tuogo.instance;

/**
 * 使用枚举来实现单例
 */
public enum EnumSingletion {
    INSTANCE;

    //1 创建私有属性
    private EnumSingletionDemo02 instance;

    //2 私有构造器 类加载的时候给这个属性赋值
    private EnumSingletion(){
        instance=new EnumSingletionDemo02();
    }
    //3 提供外界访问的入口
    public EnumSingletionDemo02 getInstance() {
        return instance;
    }
}

/**要实现单例的类
 */
class EnumSingletionDemo02{

}

main测试类代码 如下

    public static void main (String[] args) {
        EnumSingletionDemo02 instance1 = EnumSingletion.INSTANCE.getInstance();
        EnumSingletionDemo02 instance2 = EnumSingletion.INSTANCE.getInstance();
        EnumSingletionDemo02 instance3= EnumSingletion.INSTANCE.getInstance();

        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance3);
    }

总结:

  • 实现简单
  • 枚举本身就是单例模式。 由JVM从根本上提供保障! 避免了通过反射和反序列化的漏洞!
  • 缺点: 无延迟加载 

5),双重检测锁实现 (不推荐使用)

  • 因为 由于编译器优化原因和JVM底层内部模型原因偶尔会出现问题 , 不建议使用 。 
  • 这里就不做具体的展现

那我们如何选用适合哪一种单例模式呢?

  • 单例对象 占用 资源少  , 不需要  延迟加载 :  枚举类  好与 饿汉式
  • 单例对象 占用  资源大 ,    需要   延迟加载 :  静态内部类式单利模式  好与 懒汉式    

拓展

    IDEA怎么生成UML类图?(也就是对于已有的代码,想将相关类绘制成UML类图)

     第一步

  File -> Setting->Tools -> JAVA class Diagrams

      第二步

第三步生成类图

在你想生成类图的类右键 -> Diagrams -> Show Diagram...

如图

这时你可以左键类名不丢,也可以把类拖进来 

猜你喜欢

转载自blog.csdn.net/qq_40646143/article/details/85013248