专栏首页知了一笑Java描述设计模式(01):单例模式

Java描述设计模式(01):单例模式

一、单例模式简介

1、概念图解

单例设计模式定义:确保这个类只有一个实例,并且自动的实例化向系统提供这个对象。

2、样例代码

package com.model.test;
public class Singleton {
    // 使用静态变量记录唯一实例
    private static Singleton singleton = null;
    private Singleton (){}
    public static Singleton getInstance (){
        if (singleton == null){
            singleton = new Singleton() ;
        }
        return singleton ;
    }
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance() ;
        Singleton singleton2 = Singleton.getInstance() ;
        /**
         * com.model.test.Singleton@15db9742
         * com.model.test.Singleton@15db9742
         */
        System.out.println(singleton1);
        System.out.println(singleton2);
    }
}

Singleton称为单例类,构造函数使用private修饰,确保系统中只能产生一个实例,并且自动生成的。上面代码也就是所谓的懒汉式加载:只有到使用该对象的时候才来创建,意思饿了才来做饭吃。

二、线程安全问题

在上面的代码中存在一个很明显的线程安全问题,当有多条线程来请求对象实例的时候,因为对象的创建是需要时间的,假设A线程进来判断singleton == null,就会进入对象的创建过程,这时如果同时在过来几条线程,那么他们都会得到一个对象实例,这个就是所谓的线程安全问题。

1、同步控制方式

package com.model.test;
public class Singleton {
    // 使用静态变量记录唯一实例
    private static Singleton singleton = null;
    private Singleton (){}
    public static synchronized Singleton getInstance (){
        if (singleton == null){
            singleton = new Singleton() ;
        }
        return singleton ;
    }
}

这样操作会影响系统性能

2、饿汉式加载

public class Singleton {
    // 使用静态变量记录唯一实例
    private static Singleton singleton = new Singleton();
    private Singleton (){}
    public static Singleton getInstance (){
        return singleton ;
    }
}

这里先把对象创建出来,有需要直接使用;

3、双重检查

public class Singleton {
    // 使用静态变量记录唯一实例
    // volatile可以确保当singleton被初始化后,多线程才可以正确处理
    // 被volatile修饰的变量的值,将不会被本地线程缓存
    // 对该变量读写都是直接操作共享内存,确保多个线程能正确的处理该变量。
    private static volatile Singleton singleton = null ;
    private Singleton (){}
    public static Singleton getInstance (){
        // 如果实例不存在,则进入同步区
        if (singleton == null){
            // 只有第一次才会彻底执行这里面的代码
            synchronized (Singleton.class) {
                if (singleton == null){
                    singleton = new Singleton() ;
                }
            }
        }
        return singleton ;
    }
}

4、枚举方式

package com.model.design.base.node01.singleton;
import org.junit.Test;
/**
 * 类级内部类里面创建对象实例
 */
public class C06_Singleton {
 @Test
 public void test01 (){
 SingletonDemo INSTANCE1 = SingletonDemo.INSTANCE ;
 SingletonDemo INSTANCE2 = SingletonDemo.INSTANCE ;
 System.out.println(INSTANCE1 == INSTANCE2);
 INSTANCE1.info();
 INSTANCE2.info();
 }
}
enum SingletonDemo {
 INSTANCE ;
 public void info (){
 System.out.println("枚举方式实现单例");
 }
}

三、延迟类初始化

1、基础概念

1)、类级内部类   简单点说,类级内部类指的是,有static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。   类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。   类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。   类级内部类相当于其外部类的成员,只有在第一次被使用的时候才被会装载。

2)、多线程缺省同步锁   在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括:

  1.由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
  2.访问final字段时
  3.在创建线程之前创建对象时
  4.线程可以看见它将要处理的对象时

2、实现方式

要想很简单地实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程的安全性。比如前面的饿汉式实现方式,在类装载的时候就初始化对象,不管是否需要,存在一定的空间浪费。 一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,那就不会创建对象实例,从而同时实现延迟加载和线程安全。

public class LazySingleton {
    /**
     * 类级内部类
     */
    private static class SingletonHolder {
        private static LazySingleton lazySingleton = new LazySingleton() ;
    }
    public static LazySingleton getInstance (){
        return SingletonHolder.lazySingleton ;
    }
    public static void main(String[] args) {
        LazySingleton lazySingleton1 = LazySingleton.getInstance() ;
        LazySingleton lazySingleton2 = LazySingleton.getInstance() ;
        /**
         * com.model.test.LazySingleton@15db9742
         * com.model.test.LazySingleton@15db9742
         */
        System.out.println(lazySingleton1+";;"+lazySingleton2);
    }
}

四、JDK源码应用

Runtime单例实现源码。

1、案例演示

/**
 * JDK 单例模式分析
 */
public class C07_Singleton {
 public static void main(String[] args) {
 Runtime runtime1 = Runtime.getRuntime() ;
 Runtime runtime2 = Runtime.getRuntime() ;
 /*
 * 1229416514
 * 1229416514
 */
 System.out.println(runtime1.hashCode());
 System.out.println(runtime2.hashCode());
 }
}

2、源代码分析

public class Runtime {
 private static Runtime currentRuntime = new Runtime();
 public static Runtime getRuntime() {
 return currentRuntime;
 }
 private Runtime() {}
}

基于饿汉模式实现的单例模式。

五、Spring框架应用

1、创建测试类

public class UserBean {
}

2、Spring配置文件

<!-- 单例Bean -->
<bean id="user" 
class="com.model.design.spring.node01.singleton.UserBean" />

3、测试读取Bean对象

package com.model.design.spring.node01.singleton;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * Spring框架中单例模式
 */
public class S01_Singleton {
    @Test
    public void test01 (){
        ApplicationContext context01 = new ClassPathXmlApplicationContext("/spring/spring-context.xml");
        ApplicationContext context02 = new ClassPathXmlApplicationContext("/spring/spring-context.xml");
        UserBean user01 = (UserBean)context01.getBean("user") ;
        UserBean user02 = (UserBean)context01.getBean("user") ;
        UserBean user03 = (UserBean)context02.getBean("user") ;
        // com.model.design.spring.node01.singleton.UserBean@364841
        System.out.println(user01);
        // com.model.design.spring.node01.singleton.UserBean@364841
        System.out.println(user02);
        // com.model.design.spring.node01.singleton.UserBean@c4711c
        System.out.println(user03);
    }
}

结论 Spring单例模式与纯粹的单例设计模式的主要区别 尽管使用相同的类加载器来加载两个应用程序上下文,但是UserBean的实例是不一样的。也就是Spring框架中的单例对象是基于应用程序中。

六、单例模式总结

1、注意事项

单例模式注意事项和细节说明
1) 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
2) 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new Object() 的方式。
3) 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象。

2、优缺点

优点:
1、单例模式只会创建一个对象实例,减少内存消耗
2、设置全局访问点,优化共享资源的访问
缺点:
1、没有接口,很难扩展
2、不利于测试
3、与单一职责原则冲突

七、源代码地址

GitHub地址:知了一笑
https://github.com/cicadasmile/model-arithmetic-parent
码云地址:知了一笑
https://gitee.com/cicadasmile/model-arithmetic-parent

本文分享自微信公众号 - 知了一笑(cicada_smile),作者:知了一笑

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • SpringBoot2.0 整合 Dubbo框架 ,实现RPC服务远程调用

    图例说明: 1)图中小方块 Protocol, Cluster, Proxy, Service, Container, Registry, Monitor 代表...

    知了一笑
  • Java描述设计模式(23):访问者模式

    电竞是游戏比赛达到“竞技”层面的体育项目。利用电子设备作为运动器械进行的、人与人之间的智力对抗运动。通过电竞,可以提高人的反应能力、协调能力、团队精神等。但是不...

    知了一笑
  • SpringBoot2.0 整合 Shiro 框架,实现用户权限管理

    Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。作为一款安全框架Shiro的设计相当巧妙。Shiro的应用不依赖...

    知了一笑
  • 聊聊设计模式之单例模式(下)

    前言 在之前的文章《聊聊设计模式之单例模式(上)》中,笔者为大家介绍了单例模式的几种常见的实现方式,并列举了各种实现方式的优缺点。在该文章的最后,笔者指出传统...

    黄泽杰
  • 实现单例的几种方式

    懒汉式单例:通过判断单例对象是否为null和加同步关键字,保证只在第一次使用单例类创建单例类,避免创建重量级单例类慢的问题。在高并发情况下,同步关键字的使用可能...

    九州暮云
  • 设计模式(创建型模式)——单例模式

    1.定义 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 2.适用性 1)当类只能有一个实例而客户可以从一个众所周知的访问点访问它时。 2)当这个唯一实...

    甜橙很酸
  • …… are only available on JDK 1.5 and higher 错误(spring 的jdk版本检测在jdk 8下的修订)

    1、在项目中创建一个package为org.springframework.core 2、在该package下面新建JdkVersion.java,内容如下...

    ydymz
  • 用Java程序仿真模拟病毒传播过程(已开源)

    最近,在Github上面有一个 VirusBroadcast 开源项目,它利用Java程序仿真模拟了冠状病毒的传播过程「理想状态下」。

    Guide哥
  • 【开源】Java程序仿真模拟病毒传播过程(附开源地址)

    最近,在Github上面有一个 VirusBroadcast 开源项目,它利用Java程序仿真模拟了冠状病毒的传播过程「理想状态下」。

    Python进击者
  • Python第三方库安装和卸载

    系统:Windows 7 版本:Python 3.5 Python是一门简洁、优雅的语言,丰富的第三方库能让我们很多的编程任务变得更加简单。对于想要用Pyth...

    CDA数据分析师

扫码关注云+社区

领取腾讯云代金券