Java 单例模式详细解释

 更新时间:2021年11月03日 09:11:46   作者:weixin_43893423  
这篇文章主要给大家介绍了关于Java中四种单例模式的相关资料,其中包括饿汉式、懒汉式、懒汉式(双重锁)及内部类等四种,分别给出了详细的示例代码和介绍,需要的朋友们下面来一起看看吧。

饿汉式

/**
 * 饿汉式
 * 类加载到内存后,就是实例化一个单例,JVM保证线程安全
 * 简单使用:推荐使用
 * 唯一缺点:不管用与不用,类加载时就会完成实例化
 */
public class Demo01 {
	//开始先新建一个对象
    private static final Demo01 INSTANCE = new Demo01();
	//构造
    private Demo01(){};
	//调用 getInstance 方法时返回 INSTANCE,唯一创建的对象
    public static Demo01 getInstance(){
        return INSTANCE;
    }
    public static void main(String[] args) {
        Demo01 m1 = Demo01.getInstance();
        Demo01 m2 = Demo01.getInstance();
        //结果为true
        System.out.println(m1 == m2);
    }
}
单例模式(饿汉式)优点:饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。
缺点:不管用与不用,类加载时就会完成实例化,会浪费一定的内存空间
改进方法:让对象在使用的时候在进行创建。------>  懒汉式

懒汉式

/**
 * 懒汉式
 * 类加载到内存后,就是实例化一个单例,JVM保证线程不安全
 * 唯一缺点:虽然达到了按需的目的,但却带来线程不安全问题
 */
public class Demo02 {
    private static Demo02 INSTANCE ;
    private Demo02(){};
    public static Demo02 getInstance(){
        //判断 INSTANCE 是否为空
       if(INSTANCE == null){
           try{
               Thread.sleep(1);
           }catch (InterruptedException e){
               e.printStackTrace();
           }
           INSTANCE = new Demo02();
       }
       return INSTANCE;
    }
    public static void main(String[] args) {
        for(int i = 0 ; i < 100 ; i++){
            new Thread(()->
            //输出该对象的hashcode值,通过对比值是否相等来判断是不是唯一的对象
                    System.out.println(Demo02.getInstance().hashCode())
            ).start();
        }
    }
}
单例模式(懒汉式)优点:懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。
缺点:懒汉式在多个线程进行访问时有可能会出现多个不同的对象。
改进方法:对创建方法getInstance加锁    ------>   懒汉式(加锁synchronized)

懒汉式(加锁synchronized)

/**
 * 懒汉式(加锁)
 * 类加载到内存后,就是实例化一个单例,给创建对象的方法的加锁,JVM保证线程安全
 * 唯一缺点:虽然加锁之后可以保证线程是安全的,但会使得整个方法变慢。
 */
public class Demo03 {
    private static Demo03 INSTANCE ;
    private Demo03(){};
    //方法加锁
    public static synchronized Demo03 getInstance(){
        //业务逻辑
        //判断 INSTANCE 是否为空
       if(INSTANCE == null){
           try{
               Thread.sleep(1);
           }catch (InterruptedException e){
               e.printStackTrace();
           }
           INSTANCE = new Demo03();
       }
       return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }
    public static void main(String[] args) {
        for(int i = 0 ; i < 100 ; i++){
            new Thread(()->
                    System.out.println(Demo03.getInstance().hashCode())
            ).start();
        }
    }
}
单例模式(懒汉式(加锁))优点:懒汉式(加锁)可以保证线程的安全性,但是当上锁的方法getInstance中存在业务逻辑代码时,会拉低整个对象创建过程中速度。
缺点:对整个方法加锁,降低了方法运行的时间
改进方法:对创建方法的程序块进行上锁,业务逻辑代码部分不上锁  -------->懒汉式(部分加锁synchronized)

懒汉式(部分加锁synchronized)

/**
 * 懒汉式(部分加锁)
 * 类加载到内存后,就是实例化一个单例,给创建对象的方法的部分加锁,降低时间
 */
public class Demo04 {
    private static Demo04 INSTANCE ;
    private Demo04(){};
    //方法加锁
    public static Demo04 getInstance(){
        //业务逻辑
        //判断 INSTANCE 是否为空
       if(INSTANCE == null){
           //对方法的部分代码块进行上锁
           synchronized (Demo04.class){
               try{
                   Thread.sleep(1);
               }catch (InterruptedException e){
                   e.printStackTrace();
               }
               INSTANCE = new Demo04();
           }
       }
       return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }
    public static void main(String[] args) {
        for(int i = 0 ; i < 100 ; i++){
            new Thread(()->
                    System.out.println(Demo04.getInstance().hashCode())
            ).start();
        }
    }
}
单例模式(部分加锁懒汉式)优点:加快了程序的运行,只对创建对象的部分进行加锁
缺点:通过if判断后会有多个线程在等待线程资源,等第一个线程执行完成后还会进行第二个线程创建对象。
改进方法:加入两层if判断可以防止该问题出现 -------->双层检查锁

懒汉式(DCL)

/**
 * 懒汉式(DCL)
 * Double Check Lock
 */
public class Demo04 {
    private static Demo04 INSTANCE ;
    private Demo04(){};
    //方法加锁
    public static Demo04 getInstance(){
        //业务逻辑
        //判断 INSTANCE 是否为空
       if(INSTANCE == null){
           //对方法的部分代码块进行上锁
           synchronized (Demo04.class){
               //再次进行判断,检查 INSTANCE 是否为空
               if(INSTANCE == null){
                   try{
                       Thread.sleep(1);
                   }catch (InterruptedException e){
                       e.printStackTrace();
                   }
               }
               INSTANCE = new Demo04();
           }
       }
       return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }
    public static void main(String[] args) {
        for(int i = 0 ; i < 100 ; i++){
            new Thread(()->
                    System.out.println(Demo04.getInstance().hashCode())
            ).start();
        }
    }
}
单例模式(懒汉式DCL)优点:加快了对象创建的时间,同时保证了线程的安全性。
缺点:当对象发生指令重排时,第二个线程虽然拿到了对象,但是是拿到的不完整的对象,容易出现问题
改进方法:给该方法加上volatile关键字进行上锁可以防止指令重排问题。

延伸一下:为什么要用两层if判断呢?

答:因为使用两层if可以提高方法的运行速度,因为if判断消耗的时间较少,但是synchronized 消耗的时间却很大。在外面加上一层if,可以帮助过滤掉很多线程访问。

懒汉式(DCL)最终版

/**
 * 懒汉式(DCL)
 * Double Check Lock
 */
public class Demo04 {
    private static volatile Demo04 INSTANCE ;
    private Demo04(){};
    //方法加锁
    public static Demo04 getInstance(){
        //业务逻辑
        //判断 INSTANCE 是否为空
       if(INSTANCE == null){
           //对方法的部分代码块进行上锁
           synchronized (Demo04.class){
               //再次进行判断,检查 INSTANCE 是否为空
               if(INSTANCE == null){
                   try{
                       Thread.sleep(1);
                   }catch (InterruptedException e){
                       e.printStackTrace();
                   }
               }
               INSTANCE = new Demo04();
           }
       }
       return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }
    public static void main(String[] args) {
        for(int i = 0 ; i < 100 ; i++){
            new Thread(()->
                    System.out.println(Demo04.getInstance().hashCode())
            ).start();
        }
    }
}

对 INSTANCE 进行上锁可以防止指令重排,保证对象的完整性。

延伸:DCL模式为什么要加上volatile ?

答:我们要从java对象创建过程和CPU乱序执行两个方面考虑。

java对象创建过程可分为:

1:内存中分配空间
2:初始化对象
3:变量与对象关联

当发生指令重排是顺序变为

1:内存中分配空间
3:变量与对象关联
2:初始化对象

第一个线程访问时,发生指令重排,对象刚创建一半,还未对对象内部的值进行初始化赋值。此时第二个线程进行访问,此时他读取到的就是创建到一半的对象,初始化为空的对象。最终就会导致对象不完整。

静态内部类

加载外部类时不会加载内部类,只有第一次调用getInstance方法时,JVM才加载 Singleton04Holder 并初始化INSTANCE ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。

public class Demo04 {
    private Demo04 () {
    }
    private static class Demo04Holder {
        private final static Demo04 INSTANCE = new Demo04 ();
    }
    public static Demo04 getInstance() {
        return Demo04Holder.INSTANCE;
    }
    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Demo04.getInstance().hashCode());
            }).start();
        }
    }
}

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

相关文章

  • 教您如何3分钟快速搞定EasyExcel导入与导出功能

    教您如何3分钟快速搞定EasyExcel导入与导出功能

    对于EasyExcel库,我们可以使用它来实现数据的导入和导出,下面这篇文章主要给大家介绍了关于如何3分钟快速搞定EasyExcel导入与导出功能的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-01-01
  • JAVA实现二维码生成加背景图代码实例

    JAVA实现二维码生成加背景图代码实例

    这篇文章主要介绍了JAVA实现二维码生成加背景图代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • Java 中校验时间格式的常见方法

    Java 中校验时间格式的常见方法

    在实际项目开发中,跟时间参数打交道是必不可少的,为了保证程序的安全性、健壮性,一般都会对参数进行校验,其他类型的参数校验很好实现,那你知道时间参数的是怎么校验的吗,下面给大家分享Java 中校验时间格式的方法,感兴趣的朋友跟随小编一起看看吧
    2024-08-08
  • SpringBoot 把PageHelper分页信息返回给前端的方法步骤

    SpringBoot 把PageHelper分页信息返回给前端的方法步骤

    本文主要介绍了SpringBoot 把PageHelper分页信息返回给前端的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-01-01
  • 完美解决idea没有tomcat server选项的问题

    完美解决idea没有tomcat server选项的问题

    这篇文章主要介绍了完美解决idea没有tomcat server选项的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • Spring解决依赖版本不一致报错问题

    Spring解决依赖版本不一致报错问题

    许多同学经常会遇到依赖版本不一致导致代码报错,所以这篇文章就给大家详细介绍一下Spring解决依赖版本不一致报错问题,需要的朋友跟着小编一起来看看吧
    2023-07-07
  • Java调用C++程序的实现方式

    Java调用C++程序的实现方式

    这篇文章主要介绍了Java调用C++程序的实现方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Spring Cloud Gateway中netty线程池优化示例详解

    Spring Cloud Gateway中netty线程池优化示例详解

    这篇文章主要介绍了Spring Cloud Gateway中netty线程池优化示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • 解决spring-data-jpa 事物中修改属性自动更新update问题

    解决spring-data-jpa 事物中修改属性自动更新update问题

    这篇文章主要介绍了解决spring-data-jpa 事物中修改属性自动更新update问题,具有很好的参考价值,希望对大家
    2021-08-08
  • Java 超详细讲解对象的构造及初始化

    Java 超详细讲解对象的构造及初始化

    面向对象乃是Java语言的核心,是程序设计的思想。Java语言的面向对象技术包括了面向对象和面向过程的基本概念,面向对象的特征,Java语言的类,对象,修饰符,抽象类等一系列的知识点
    2022-03-03

最新评论