A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

一、单例模式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


2 个回复

倒序浏览
有任何问题欢迎在评论区留言
回复 使用道具 举报
或者添加学姐微信
DKA-2018
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马