首页 > 其他分享 >单例模式

单例模式

时间:2023-01-17 13:32:52浏览次数:39  
标签:getInstance 模式 SingletonTest04 instance static 单例 public

单例模式

1、概述

核心作用:

保证一个类只有一个实例,并且提供一个访问该实例的全局访问点

常见场景:

  • Window的任务管理器
  • Window的回收站
  • 项目中,读取配置文件的类,一般也只有一个对象,没必要每次都去new对象读取
  • 网站的计数器一般也会采用单例模式,可以保证同步
  • 数据库连接池的设计一般也是单例模式
  • 在Servlet编程中,每个Servlet也是单例的
  • 在Spring中,每个Bean默认就是单例的

优点:

  • 由于单例模式只生成一个实例,减少了系统性能开销
  • 单例模式可以在系统设置全局访问点,优化共享资源访问

常见的五种单例模式实现方式:

  • 饿汉式(线程安全,调用效率高,不能延时加载)
  • 懒汉式(线程安全,调用效率不高,可以延时加载)
  • DCL懒汉式(由于JVM底层内部模型原因,偶尔会出现问题,不建议使用)
  • 饿汉式改进、静态内部类式(线程安全,调用效率高,可以延时加载)
  • 枚举单例(线程安全,调用效率高,不能延时加载)

2、饿汉式

饿汉式代码:

package pers.mobian.singleton;

//饿汉式单例
public class SingletonTest01 {
    //1.私有化构造器
    private SingletonTest01() {
    }

    //2.类初始化的时候,立即加载该类的对象
    private static SingletonTest01 instance = new SingletonTest01();

    //3.提供获取该对象的方法,此处没有synchronized,效率高
    public static SingletonTest01 getInstance() {
        return instance;
    }
}

class Test {
    public static void main(String[] args) {
        SingletonTest01 instance1 = SingletonTest01.getInstance();
        SingletonTest01 instance2 = SingletonTest01.getInstance();
        System.out.println(instance1 == instance2);
    }
}

测试结果:

true

总结:

此种单例模式十分简单,但是如果在SingletonTest01类中,存在初始化的数据,每次仅仅初始化类,并未真真调用该类时,就会出现占用空间的现象。代码如下:

public class SingletonTest01 {
    byte[] a1 =  new byte[1024];
    byte[] a2 =  new byte[1024];
    byte[] a3 =  new byte[1024];
    private SingletonTest01() {
    }
    private static SingletonTest01 instance = new SingletonTest01();
    public static SingletonTest01 getInstance() {
        return instance;
    }
}

于是出现了懒汉式单例模式。


3、懒汉式

懒汉式代码:

package pers.mobian.singleton;

public class SingletonTest02 {
    byte[] a1 = new byte[1024];
    byte[] a2 = new byte[1024];
    byte[] a3 = new byte[1024];

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

    //2.类初始化的时候,立即加载该类的对象
    private static SingletonTest02 instance;

    //3.提供获取该对象的方法,此处有synchronized,效率较低
    public static synchronized SingletonTest02 getInstance() {
        if (instance == null) {
            instance = new SingletonTest02();
        }
        return instance;
    }

}

class Test1 {
    public static void main(String[] args) {
        SingletonTest02 instance1 = SingletonTest02.getInstance();
        SingletonTest02 instance2 = SingletonTest02.getInstance();
        System.out.println(instance1 == instance2);
    }
}

测试结果:

true

总结:

此方法可以延时加载,仅仅调用该方法后,才会初始方法区中的数据,继而避免出现饿汉式单例模式的问题。但是使用了synchronized关键字,在达到了线程安全的同时会出现效率低下的问题,继而引入了DCL(双重检测锁)懒汉式。


4、DCL(Double Check Lock)懒汉式

DCL懒汉式代码:

package pers.mobian.singleton;

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

    //2.类初始化的时候,立即加载该类的对象
    //此处还需要加上volatile关键字:此关键字直接指向内存,防止在运行第三步的时候,出现多个线程运行到synchronized代码块后,if判断语句之前
    private volatile static SingletonTest03 instance;

    //因为此操作不是原子性操作,所以在内存中会出现这样三步的操作
    /*
    * 1.分配内存
    * 2.执行构造方法
    * 3.指向地址
    * */
    //这也就是为什么要使用volatile关键字
    //3.提供获取该对象的方法,此处没有synchronized,效率较低
    public static SingletonTest03 getInstance() {
        //此处为第一重锁,判断对象是否被创建
        if (instance == null) {
            synchronized (SingletonTest03.class) {
                //此为第二重锁,判断自己是否是第一个拿到锁的,继而创建对象
                if (instance == null) {
                    instance = new SingletonTest03();
                }
            }
        }
        return instance;
    }
}

class Test3 {
    public static void main(String[] args) {
        SingletonTest03 instance1 = SingletonTest03.getInstance();
        SingletonTest03 instance2 = SingletonTest03.getInstance();
        System.out.println(instance1 == instance2);
    }
}

测试结果:

true

总结:

使用此单例模式,可以避免出现懒汉式单例synchronized关键字将整个方法锁住,继而影响效率。DCL单例中的synchronized关键字只锁了if判断成功以后的代码块,如果instance不是null,则会跳过直接return,不会降低效率。


5、饿汉式改进、静态内部类式

饿汉式改进、静态内部类式代码:

package pers.mobian.singleton;
//静态内部类实现
public class SingletonTest04 {
    private SingletonTest04(){}
    private static class InnerClass{
        private static final SingletonTest04 instance = new SingletonTest04();
    }
    public static SingletonTest04 getInstance(){
        return InnerClass.instance;
    }
}

class Test4{
    public static void main(String[] args) {
        SingletonTest04 instance1 = SingletonTest04.getInstance();
        SingletonTest04 instance2 = SingletonTest04.getInstance();
        System.out.println(instance1 == instance2);
    }
}

测试结果:

true

总结:

采用此方法,可以延时加载,并且线程安全。但是java中存在反射机制,能够改变内部的private关键字,于是引入了枚举单例模式

反射破坏单例模式测试代码:

package pers.mobian.singleton;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

//静态内部类实现
public class SingletonTest04 {
    private SingletonTest04(){}
    private static class InnerClass{
        private static final SingletonTest04 instance = new SingletonTest04();
    }
    public static SingletonTest04 getInstance(){
        return InnerClass.instance;
    }
}

class Test4{
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        //利用反射获取其对应的构造器方法
        Constructor<SingletonTest04> declaredConstructor = SingletonTest04.class.getDeclaredConstructor(null);
        //开启访问权限
        declaredConstructor.setAccessible(true);
        //直接调用方法,进行实例化输出
        SingletonTest04 instance1 = SingletonTest04.getInstance();
        SingletonTest04 instance2 = declaredConstructor.newInstance();
        System.out.println(instance1 == instance2);
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

执行结果:

false
2137211482
920011586

6、枚举单例

newInstance类源码:

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, clazz, modifiers);
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            //此行代码表示,不能反射枚举类型的对象
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

枚举单例测试代码:

package pers.mobian.singleton;

public enum SingletonTest05 {
    INSTANCE;
    public SingletonTest05 getInstance(){
        return INSTANCE;
    }
}
class test05{
    public static void main(String[] args) {
        SingletonTest05 instance1 = SingletonTest05.INSTANCE;
        SingletonTest05 instance2 = SingletonTest05.INSTANCE;
        System.out.println(instance1==instance2);
    }
}

测试结果:

true

总结:

利用枚举的方式创建单例模式,可以避免反射对其内部进行关键字的破坏,继而导致单例模式失效。它的缺点也是不能延时加载


7、总结

不同的单例模式拥有不同的运用场景,灵活运用。将单例模式的思想带入到我们的编码中,才是学习设计模式的关键。

标签:getInstance,模式,SingletonTest04,instance,static,单例,public
From: https://blog.51cto.com/u_15942107/6017180

相关文章

  • 22.(行为型模式)java设计模式之备忘录模式
    一、什么是备忘录模式(MementoPattern)定义:在不破坏封闭的前提下,捕获⼀个对象的内部状态,保存对象的某个状态,以便在适当的时候恢复对象,⼜叫做快照模式,属于⾏为模式。备......
  • 04-代理模式
    04代理模式背景本博客是照着程杰的《大话设计模式》一书实现的Java代码的版本,再加自己的一点理解问题卓贾易追求娇娇的方式是派出自己的好友戴笠实现该模型的代码逻辑......
  • 读书笔记:价值投资.03.商业模式是什么
    商业模式是什么这个世界上有很多公司,靠商业模式赚钱.比如Uber,滴滴,几乎不拥有出租车,却是市场上最大的出租车公司.比如airbnb,几乎不拥有任何一家酒店,却是全球......
  • 设计模式之单例模式
    一、什么是单例模式?单例设计模式属于创建型模式范畴,所以主要用于处理对象创建和操作,当我们需要确保只创建一个特定类的实例,然后为整个应用程序提供对该实例的简单全局访问......
  • 设计模式——概览
     设计模式分二十三种,三大类,分别是:对象创建型抽象工厂、生成器、工厂方法、原型、单例结构型适配器、桥接、组合、装饰器、外观、享元、代理行为型责任链、......
  • 手写笔记22:代理模式
    ......
  • 18.(行为型模式)java设计模式之观察者模式
    一、什么是观察者模式定义对象间⼀种⼀对多的依赖关系,使得每当⼀个对象改变状态,则所有依赖于它的对象都会得到通知并⾃动更新,也叫做发布订阅模式Publish/Subscribe,属于⾏......
  • 学习模式-组合模式
    组合模式组合模式(CompositePattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的......
  • 13.(行为型模式)java设计模式之模板模式
    一、什么是模板模式定义⼀个操作中的算法⻣架,将算法的⼀些步骤延迟到⼦类中,使得⼦类可以不改变该算法结构的情况下重定义该算法的某些特定步骤,属于⾏为型模式二、模板模......
  • 学习记录-迭代器模式
    迭代器模式迭代器模式(IteratorPattern)是Java和.Net编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。迭代器模式......