• Java设计模式---单例模式学习笔记


    单例模式简介

    单例模式就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
    1. 单例模式保证了系统的内存中该类只有一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
    2. 当实例化一个单例类的时候,要使用相应的getInstance方法而不是new一个新的

    单例模式的总体步骤:

    1. 构造器私有化
    2. 类的内部创建对象
    3. 向外暴露一个静态的公共方法
    4. 代码实现

    单例模式的8种方式

    • 饿汉式
      • 饿汉式(静态常量)
      • 饿汉式(静态代码块)
    • 懒汉式
      • 懒汉式(线程不安全)
      • 懒汉式(线程安全,同步方法)
      • 懒汉式(线程安全,同步代码块)
    • 双重检查
    • 静态内部类
    • 枚举

    饿汉式(静态常量)

    //饿汉式(静态变量)
    public class SingletonTest01 {
        public static void main(String[] args) {
            //测试
            SingleTon instance1 = SingleTon.getInstance();
            SingleTon instance2 = SingleTon.getInstance();
            //其实拿到的是同一个实例对象
            System.out.println(instance1==instance2);
            //比较哈希码
            System.out.println("instance1.hashCode()="+instance1.hashCode());
            System.out.println("instance2.hashCode()="+instance2.hashCode());
        }
    }
    class SingleTon {
        //1.构造器私有化
        private SingleTon() {
    
        }
        //2.在本类内部创建对象实例
        private final static SingleTon instance = new SingleTon();
        //3.对外提供一个公有的静态方法,返回实例对象
        public static SingleTon getInstance() {
            return instance;
        }
    }
    
    

    优点和缺点

    • 优点:应为是静态的方法和变量,所以在类装载的时候就完成了实例化,避免了线程同步问题.
    • 缺点:在类装在的时候就完成实例化,没有达到Lazy Loading的效果.如果从始至终都没有使用这个实例,就会造成空间的浪费
    • 可能造成内存浪费

    饿汉式(静态代码块)

    与静态变量的写法类似,只不过在静态代码块中创建了新的单例对象

    //饿汉式(静态代码块)
    public class SingletonTest02 {
        public static void main(String[] args) {
            //测试
            SingleTon instance1 = SingleTon.getInstance();
            SingleTon instance2 = SingleTon.getInstance();
            //其实拿到的是同一个实例对象
            System.out.println(instance1==instance2);
            //比较哈希码
            System.out.println("instance1.hashCode()="+instance1.hashCode());
            System.out.println("instance2.hashCode()="+instance2.hashCode());
        }
    }
    
    class SingleTon {
        //1.构造器私有化
        private SingleTon() {}
        //2.在本类内部创建对象实例
        private static SingleTon instance;
        
        static {
            //在静态代码块中创建单例对象
            instance = new SingleTon();
        }
        //3.对外提供一个公有的静态方法,返回实例对象
        public static SingleTon getInstance() {
            return instance;
        }
    }
    
    

    优点和缺点

    • 优点:应为是静态的方法和变量,所以在类装载的时候就完成了实例化,避免了线程同步问题.
    • 缺点:在类装在的时候就完成实例化,没有达到Lazy Loading的效果.如果从始至终都没有使用这个实例,就会造成空间的浪费
    • 可能造成内存浪费
    • 优缺点与静态变量的写法类似

    懒汉式(线程不安全)

    • 解决了性能的问题,只有用到了实例,实例才会被创建
    • 其实是在getInstance中加入了一个判断,如果实例为null则创建,否则直接返回实例
    • 但是在多线程中可能会出错
    public class SingletonTest03 {
        public static void main(String[] args) {
            System.out.println("懒汉式,线程不安全");
            //测试
            SingleTon instance1 = SingleTon.getInstance();
            SingleTon instance2 = SingleTon.getInstance();
            //其实拿到的是同一个实例对象
            System.out.println(instance1==instance2);
            //比较哈希码
            System.out.println("instance1.hashCode()="+instance1.hashCode());
            System.out.println("instance2.hashCode()="+instance2.hashCode());
        }
    }
    
    class SingleTon {
        private SingleTon () {}    //构造私有化
        private static SingleTon instance;    //内部创建对象
        //提供一个静态的公有方法,当使用到该方法的时候才创建intance
        //即懒汉式
        public static SingleTon getInstance() {
            if(instance==null) {    //就是在这里加了一个判断,但是如果最开始两个或多个线程同时进入到这里,实例还没创建好,可能判断结果都为真,就会创建多个实例
                instance = new SingleTon();
            }
            return instance;
        }
    }
    
    

    优点和缺点

    • 优点:起到了Lazy Loading的效果
    • 缺点:在多线程下,一个线程进入了if判断,还没来得及执行,另一个线程也进入了这个判断,这个时候就会产生多个实例但是只能在单线程下使用
    • 在实际开发中,不要使用这种方式

    懒汉式(线程安全,同步方法)

    其实就是在线程不安全的基础上加了synchronized关键字
    public class SingletonTest04 {
        public static void main(String[] args) {
            System.out.println("懒汉式(2),线程安全");
            //测试
            SingleTon instance1 = SingleTon.getInstance();
            SingleTon instance2 = SingleTon.getInstance();
            //其实拿到的是同一个实例对象
            System.out.println(instance1==instance2);
            //比较哈希码
            System.out.println("instance1.hashCode()="+instance1.hashCode());
            System.out.println("instance2.hashCode()="+instance2.hashCode());
        }
    }
    
    class SingleTon {
        private static SingleTon instance;
        private SingleTon () {}
        //提供一个静态的公有方法 加入了同步处理的代码,解决的了线程安全问题
        //即懒汉狮
        public static synchronized SingleTon getInstance() {
            if(instance==null) {
                instance = new SingleTon();
            }
            return instance;
        }
    }
    
    

    优点和缺点

    • 优点:解决了线程安全问题
    • 缺点:效率很低, 程序运行的过程中, getInstance()方法肯定要被执行很多次,并且都要进行同步
    • 其实只要执行一次实例化,后面的想要获得实例,直接return
    • 在实际开发中,不推荐使用这种方式

    双重检查

    • 有两次检查是否创建了实例
    • 前面一个是判断是否已经创建了对象
    • 后面一个是用了同步的方式,保证只有一个线程创建实例
    public class SingletonTest06 {
        public static void main(String[] args) {
            System.out.println("双重检查");
            //测试
            SingleTon instance1 = SingleTon.getInstance();
            SingleTon instance2 = SingleTon.getInstance();
            //其实拿到的是同一个实例对象
            System.out.println(instance1==instance2);
            //比较哈希码
            System.out.println("instance1.hashCode()="+instance1.hashCode());
            System.out.println("instance2.hashCode()="+instance2.hashCode());
        }
    }
    
    class SingleTon {
        private static volatile SingleTon instance;    //保持内存可见,防止指令重排(一个实例创建好之后其余的线程可以立马知道)
        private SingleTon () {}
        //提供一个静态的公有方法 加入双重检查的代码,解决的了线程安全问题,同时解决懒加载问题
        public static SingleTon getInstance() {
            if(instance==null) {
                synchronized (SingleTon.class) {    //同步,每次只有一个线程进去
                    if(instance==null) {
                        instance = new SingleTon();
                    }
                }
            }
            return instance;
        }
    }
    
    

    优点和缺点

    • 实例化代码只执行了一次,后面再次访问的时候直接return实例化对象,避免了反复进行方法同步
    • 线程安全;延迟加载;效率较高
    • 推荐使用

    静态内部类

    • 初始化实例的时候只有一个线程
    • 装载时不会实例化,调用了getInstance方法时才会实例化
    public class SingletonTest07 {
        public static void main(String[] args) {
            System.out.println("静态内部类完成");
            //测试
            SingleTon instance1 = SingleTon.getInstance();
            SingleTon instance2 = SingleTon.getInstance();
            //其实拿到的是同一个实例对象
            System.out.println(instance1==instance2);
            //比较哈希码
            System.out.println("instance1.hashCode()="+instance1.hashCode());
            System.out.println("instance2.hashCode()="+instance2.hashCode());
        }
    }
    //静态内部类 推荐使用
    class SingleTon {
        private static SingleTon instance;
        //构造器私有化
        private SingleTon () {}
        //写一个静态内部类,该类中有一个静态的属性SingleTon
        private static class SingleTonInstance {
            //装载是安全的
            private static final SingleTon INSTANCE = new SingleTon();
        }
    
        //提供一个静态的公有方法,直接返回SingleTonInstance的成员变量
        public static SingleTon getInstance() {
            return SingleTonInstance.INSTANCE;
        }
    }
    

    优点和缺点

    • 类装载的机制保证了初始化的时候只有一个线程
    • 在SingleTon 被装载的时候不会立即实例化,而是在需要实例化时,调用getInstance才会装载,从而完成实例化

    枚举

    • 最简单的写法
    public class SingleTonTest08 {
        public static void main(String[] args) {
            SingleTon instance1 = SingleTon.INSTANCE;
            SingleTon instance2 = SingleTon.INSTANCE;
            System.out.println(instance1==instance2);
            System.out.println(instance1.hashCode());
            System.out.println(instance2.hashCode());
            instance1.sayOK();
        }
    }
    //使用枚举可以使用单例
    enum SingleTon {
        INSTANCE;   //属性,保证是一个单例
        public void sayOK() {
            System.out.println("OK~");
        }
    }
    
    • 避免了多线程同步问题,还可以防止反序列化重新创建新的对象
  • 相关阅读:
    【Golang】 关于Go 并发之三种线程安全的 map
    Halodoc使用 Apache Hudi 构建 Lakehouse的关键经验
    Logstash优化吞吐率
    ES集群状态yellow修复
    ES集群状态red修复
    vxetable 导出Excel 超过100条,自定义模板template替换失效
    [转]使用minio搭建自己的文件存储服务(新版和旧版)
    [转]vue 预览pdf,txt
    windows7 使用nvs升级降级node.js版本
    新建Token来访问K8S apiserver
  • 原文地址:https://www.cnblogs.com/xun-/p/12821314.html
Copyright © 2020-2023  润新知