Java 设计模式——单例模式 理论代码相结合

Posted 守夜人爱吃兔子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 设计模式——单例模式 理论代码相结合相关的知识,希望对你有一定的参考价值。

一、前言

概念:

单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)--来自百度百科

应用:

单例模式可以让我们只创建一个对象从而避免了频繁创建对象导致的内存消耗和垃圾回收。

单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:

  1. 需要频繁实例化然后销毁的对象。
  2. 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  3. 有状态的工具类对象。
  4. 频繁访问数据库或文件的对象。

项目中的具体应用:

  1. 封装一些常用的工具类,保证整个应用常用的数据统一
  2. 保存一些共享数据在内存中,其他类随时可以读取。

实现单例模式的原则和过程:

  1. 单例模式:确保一个类只有一个实例,自行实例化并向系统提供这个实例
  2. 单例模式分类:饿单例模式(类加载时实例化一个对象给自己的引用),懒单例模式(调用取得实例的方法如getInstance时才会实例化对象)
  3. 单例模式要素: a.私有构造方法 b.私有静态引用指向自己实例 c.以自己实例为返回值的公有静态方法

方式:

单例模式有八种方式:

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全方式)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程不安全,同步代码块)
  6. 懒汉式(双重检查)
  7. 懒汉式(静态内部类)
  8. 枚举实现

二、单例模式代码实现及分析

2.1、饿汉式(静态常量)

代码实现:

/**
 * 单例模式
 * @Author: crush
 * @Date: 2021-08-06 9:14
 * version 1.0
 */
public class SingletonTest1 {
    public static void main(String[] args) {
        // 获取两次,看获取到的对象 确实是单例的吗
        Singleton singleton = Singleton.getInstance();
        Singleton singleton1 = Singleton.getInstance();

        System.out.println(singleton == singleton1);

        System.out.println("singleton  hashcode:"+singleton.hashCode());
        System.out.println("singleton1 hashcode:"+singleton1.hashCode());
        /**
         * 输出:
         * true
         * singleton  hashcode:24324022
         * singleton1 hashcode:24324022
         */
    }
}

/**
 *  1、饿汉式(静态常量)代码实现
 */
class Singleton{
    /*** 构造器私有化*/
    private Singleton(){};
    
    /** * 在类的内部创建一个对象实例 随当前类加载而加载 没有线程安全问题。 */
    private final static Singleton INSTANCE=new Singleton();
    
    /*** 再提供一个 公有的方法来返回这个静态常量*/
    public static Singleton getInstance(){
        return INSTANCE;
    }
}

结论及优缺

  1. 优点:饿汉式(静态常量方式)它不用担心线程安全问题,它本身就是在类的装载的时候完成实例化的。

  2. 缺点:但是缺点也在这个地方,不管用不用,只要类装载了,那么他就会直接完成实例化,不能达到懒加载的效果,如果你从始至终都没有使用过这个类,那么就会造成内存的浪费。

    注意:你可能会想类都已经加载了,为什么我还会不用到呢?

    在单例模式中大都调用getInstance方法,getInstance这个静态方法可以让类加载,那么同样的道理,如果这个类中还有其他的静态方法,你调用它的时候,同样会使类进行装载。

  3. 小结:这种单例方式,其实还是可以用的,至少不用担心线程同步问题,那种使用特别频繁的类用这种方式还是没啥问题的,除了可能会导致内存浪费

2.2、饿汉式(静态代码块)

/**
 * 单例模式 2
 *
 * @Author: crush
 * @Date: 2021-08-06 9:14
 * version 1.0
 */
public class SingletonTest1 {
    public static void main(String[] args) {
        // 我们去拿两次,看获取到的对象 确实是单例的吗
        Singleton singleton = Singleton.getInstance();
        Singleton singleton1 = Singleton.getInstance();

        System.out.println(singleton == singleton1);

        System.out.println("singleton  hashcode:" + singleton.hashCode());
        System.out.println("singleton1 hashcode:" + singleton1.hashCode());
        /**
         * 输出:
         * true
         * singleton  hashcode:24324022
         * singleton1 hashcode:24324022
         */
    }
}

/**
 * 1、饿汉式(静态代码块)代码实现
 */
class Singleton {

    /** * 构造器私有化 */
    private Singleton() {
    }
    /** * 在类的内部创建一个对象实例 随当前类加载而加载 没有线程安全问题。*/
    private static Singleton singleton;

    static {
        //改为在静态代码块中 创建单例对象
        singleton = new Singleton();
    }

    /** * 再提供一个 公有的方法来返回这个静态常量  */
    public static Singleton getInstance() {
        return singleton;
    }
}

结论:这种方式其实和第一种非常类似,只是将类的实例化过程放进静态代码块而已。优缺点同饿汉式(静态常量)。

2.3、懒汉式(线程不安全)

/**
 * 单例模式
 *
 * @Author: crush
 * @Date: 2021-08-06 9:14
 * version 1.0
 */
public class SingletonTest3 {
    public static void main(String[] args) {

        //懒汉式  线程不安全方式,适合单线程使用
        //===========单线程下是安全的 ,测试代码和第一种一样===========
        // =========模拟多线程下=============
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                Singleton instance = Singleton.getInstance();
                System.out.println(instance.hashCode());
            }
        };

        Runnable runnable2 = new Runnable(){
            @Override
            public void run() {
                Singleton instance1 = Singleton.getInstance();
                System.out.println(instance1.hashCode());
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
        /**
         * 结果并不唯一,
         * 可能会出现相同,也有可能不同,多测几次,就能发现是线程不安全的。
         * 94433
         * 21648409
         */
    }
}

/**
 * 懒汉式 线程不安全方式
 */
class Singleton {

    /*** 构造器私有化*/
    private Singleton() {}
    
    /*** 在类的内部创建一个对象实例 随当前类加载而加载 没有线程安全问题。*/
    private static Singleton singleton;

    /**
     * 提供一个公有的方法
     * 当使用到这个方法时,才去创建singleton
     */
    public static Singleton getInstance() {
        if(singleton==null){
            // 通过在这里堵赛的方式来模拟多线程
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton= new Singleton();
        }
        return singleton;
    }
}

结论及优缺:

  1. 优点:起到了懒加载的效果
  2. 缺点:线程不安全,如果在多线程下,第一个线程进入到if(singleton==null)下,但还未来的及执行,第二个线程就紧随而来也进入了if(singleton==null)下,那么就会创建多个实例。就不是单例模式了。
  3. 建议:开发不要使用这种方式,线程安全问题不能解决,就是😱。

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

/**
 * 单例模式
 *
 * @Author: crush
 * @Date: 2021-08-06 9:14
 * version 1.0
 */
public class SingletonTest4 {
    public static void main(String[] args) {

        //懒汉式  线程不安全方式,适合单线程使用
        //===========单线程下 单线程是安全的===========
        // =========模拟多线程下=============
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                Singleton instance = Singleton.getInstance();
                System.out.println(instance.hashCode());
            }
        };

        Runnable runnable2 = new Runnable(){
            @Override
            public void run() {
                Singleton instance1 = Singleton.getInstance();
                System.out.println(instance1.hashCode());
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
        /**
         6902734
         6902734
         */
    }
}

/**
 * 2、懒汉式 线程安全方式
 */
class Singleton {

    /*** 构造器私有化 */
    private Singleton() {}
    
    /*** 在类的内部创建一个对象实例 随当前类加载而加载 没有线程安全问题。*/
    private static Singleton singleton;


    /**
     * 提供一个公有的方法
     * 当使用到这个方法时,才去创建singleton
     * 加上 synchronized 关键字 解决线程安全问题
     */
    public static synchronized Singleton getInstance() {
        if(singleton==null){
            // 通过在这里堵赛的方式来模拟多线程
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton= new Singleton();
        }
        return singleton;
    }
}

结论及优缺:

  • 其实代码和懒汉式线程不安全的实现,就是在Singleton getInstance() 上多了一个了synchronized,将这个方法变成了同步方法,解决了线程同步问题。
  • 缺点:但是因为在方法上加了synchronized 关键字,导致执行效率的降低。并且之后每次来获取,都要进行同步,但其实本质上这段代码执行一次,之后都是retrun 是最佳的,而方法进行同步就大大降低效率拉。
  • 不推荐这种方式,虽然做到线程同步,但效率太低,影响使用。

2.5、懒汉式(线程并不安全的同步代码块)

package com.crush.singleton05;

/**
 * 单例模式
 *
 * @Author: crush
 * @Date: 2021-08-06 9:14
 * version 1.0
 */
public class SingletonTest5 {
    public static void main(String[] args) {

        //懒汉式  线程不安全方式,适合单线程使用
        //===========单线程下是安全的,代码同上===========
        // =========模拟多线程下=============
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Singleton instance = Singleton.getInstance();
                System.out.println(instance.hashCode());
            }
        };

        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                Singleton instance1 = Singleton.getInstance();
                System.out.println(instance1.hashCode());
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
        /**
         *  如果这样的话  多线程是并不安全的。
         20775718
         5987586
         */
    }
}

/**
 * 2、懒汉式 网上有说这是线程安全的问题,也有说不是的
 */
class Singleton {

    /** * 构造器私有化*/
    private Singleton() {
    }

    /*** 在类的内部创建一个对象实例 随当前类加载而加载 没有线程安全问题。 */
    private static Singleton singleton;

    /**
     * 提供一个公有的方法
     * 当使用到这个方法时,才去创建singleton
     * 在同步代码块 处添加 synchronized
     */
    public static Singleton getInstance() {
        if (singleton == null) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Singleton.class) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}

结论及优缺:

1)其实本意是对上一种方式做一个优化,想要提高同步效率,改为这种同步代码块的方式

2)但实际上,这并不能起到线程同步的作用,跟上一种方式遇到的问题是一样的。

3)同样不建议采取这种方式来实现单例模式。

2.6、懒汉式(双重检查)

/**
 * 单例模式
 *
 * @Author: crush
 * @Date: 2021-08-06 9:14
 * version 1.0
 */
public class SingletonTest6 {
    public static void main(String[] args) {

        //懒汉式  线程安全方式,适合单、多线程使用
        //===========单线程下是安全的,代码同上===========
        // =========模拟多线程下=============
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Singleton instance = Singleton.getInstance();
                System.out.println(instance.hashCode());
            }
        };

        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                Singleton instance1 = Singleton.getInstance();
                System.out.println(instance1.hashCode());
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
        /**
         *  线程安全
         * 7739563
         * 7739563
         */
    }
}

/**
 * 2、懒汉式 双重检查 线程安全
 */
class Singleton {

    /*** 构造器私有化*/
    private Singleton() {
    }

    /*** 在类的内部创建一个对象实例 随当前类加载而加载 没有线程安全问题。*/
    private static Singleton singleton;


    /**
     * 提供一个公有的方法
     * 当使用到这个方法时,才去创建singleton
     * 在同步代码块 处添加 synchronized
     * 双重检查
     */
    public static Singleton getInstance() {
        if (singleton == null) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Singleton.class) {
                if(singleton==null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

结论及优缺:

1)我们在代码中进行了两次if (singleton == null)操作,一次在同步代码块外,一次在同步代码块内,两次检查,保证了线程的安全。

2)这样的话,同步代码只要执行一次,singleton也只需实例化一次,既做到了懒加载,又同时保证线程安全,提高了执行效率。

3)结论:懒加载、线程安全、效率较高,当然用这种方式啊。

2.7、懒汉式(静态内部类)

/**
 * 单例模式
 *
 * @Author: crush
 * @Date: 2021-08-06 9:14
 * version 1.0
 */
public class SingletonTest7 {
    public static void main(String[] args) {

        //懒汉式  线程安全方式,适合单、多线程使用
        //===========单线程下是安全的 和上面一样的===========
        // =========模拟多线程下=============
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Singleton instance = Singleton.getInstance();
                System.out.println(instance.hashCode());
            }
        };

        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                Singleton instance1 = Singleton.getInstance();
                System.out.println(instance1.hashCode());
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
        /**
         *  线程安全
         * 7739563
         * 7739563
         */
    }
}

/**
 * 2、懒汉式 静态内部类方式
 */
class Singleton {

    /*** 构造器私有化*/
    private Singleton() {
    }

    /** * 写一个静态内部类,然后在类的内部再写一个静态常量 Singleton */
    private static class SingletonInstance {
        private final static Singleton SINGLETON=new Singleton();
    }


    /**
     * 提供一个公有的方法
     * 当使用到这个方法时,才去加载 SingletonInstance 获得 Singleton 实例 
     */
    public static Singleton getInstance() {
        return SingletonInstance.SINGLETON;
    }
}

结论及优缺:

1)这种方式同样不会产生线程同步问题,也是借用JVM的类装载的机制来保证实例化的时候只有一个线程。

2)静态内部类SingletonInstanceSingleton被装载时,并不会立即实例化,而是在需要实例化的时候,调用了getInstance 方法,才会进行 SingletonInstance类的装载。

3)类的静态属性只会在第一次加载类的时候进行初始化,而在这里,JVM的类装载机制帮助我们保证了线程安全性。

4)小结:避免了线程安全问题、利用了静态内部类延迟加载(做到懒加载)、效率高,这不更爽了吗,用起来。

2.8、枚举类实现

/**
 * 单例模式
 *
 * @Author: crush
 * @Date: 2021-08-06 9:14
 * version 1.0
 */
public class SingletonTest8 {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.SINGLETON;
        Singleton singleton2 = Singleton.SINGLETON;

        System.out.println(singleton1 == singleton2);
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }
}

/**
 * 2、 枚举方式
 */
enum Singleton {
    SINGLETON;
}

结论及优缺:

  • 没有多线程问题,效率高,能够防止反序列化重新创建对象,也是推荐使用的方式。

:一般来说,大都采用饿汉式,并竟方便,如果十分在意资源的话,一般采用静态内部类。但是一切的使用,都是具体问题具体分析,没有最好的,只有最适合的。

三、单例在一些源码中的使用

3.1、JDK

像JDK中的 Runtime就是使用了饿汉式单例方式实现

3.2、Mybatis

Mybatis中 ErrorContextThreadLocal基于线程唯一

Spring中也有蛮多哈,没有一一去找了。

四、最后

一直想整理出一份完美的面试宝典,但是时间上一直腾不开,这套一千多道面试题宝典,结合今年金三银四各种大厂面试题,以及 GitHub 上 star 数超 30K+ 的文档整理出来的,我上传以后,毫无意外的短短半个小时点赞量就达到了 13k,说实话还是有点不可思议的。

一千道互联网 Java 工程师面试题

内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、mysql、Spring、SpringBoot、SpringCloud、RabbitMQ、Kafka、Linux等技术栈(485页)

《Java核心知识点合集(283页)》

内容涵盖:Java基础、JVM、高并发、多线程、分布式、设计模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat、数据库、云计算等

《Java中高级核心知识点合集(524页)》

《Java高级架构知识点整理》

 

 由于篇幅限制,详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

需要的小伙伴,可以一键三连,点击这里获取免费领取方式

以上是关于Java 设计模式——单例模式 理论代码相结合的主要内容,如果未能解决你的问题,请参考以下文章

二万字长文图文详解RabbitMQ6 种工作模式(理论与代码相结合)

单例模式和排序算法

Java面向对象--单例(Singleton)设计模式和main方法

单例模式与多线程

java学习日记20230323-单例设计模式

Java自用设计模式---单例设计模式