java:单例模式的五种实现方式[通俗易懂]

java:单例模式的五种实现方式[通俗易懂]基于java实现到单例模式

大家好,又见面了,我是你们的朋友全栈君。

1. 概述

java单例模式是一种常见的设计模式。

单例模式有以下特点:

  1. 单例类只能有一个实例;
  2. 单例类必须自己创建自己的唯一实例;
  3. 单例类必须给所有其他对象提供这一实例;

2. 优缺点

  • 优点:由于单例模式只生成了一个实例,所以能够节约系统资源,减少性能开销,提高系统运行效率。
  • 缺点:因为系统中只有一个实例,导致了单例类的职责过重,违背了“单一职责原则”,同时不利于扩展。

3. 单例模式实现方式

常见的单例模式实现方式有五种:饿汉式懒汉式双重检测锁静态内部类枚举单例

3.1 饿汉式

public class SingletonDemoInHunger { 
   
	
	// 私有实例,类初始化就加载
	private static SingletonDemoInHunger instance = new SingletonDemoInHunger();
	
	// 私有构造方法
	private SingletonDemoInHunger() { 
   }
	
	// 公共的、静态的获取实例方法
	public static SingletonDemoInHunger getInstance() { 
   
		return instance;
	}
}

饿汉式

  • 类加载时就初始化,浪费内存,不能延迟加载
  • 基于 classloader 机制避免了多线程的同步问题,线程安全
  • 没有加锁,调用效率高

3.2 懒汉式

public class SingletonDemoInLazy { 
   
	
	// 私有实例,初始化的时候不加载(延迟加载)
	private static SingletonDemoInLazy instance;
	
	// 私有构造
	private SingletonDemoInLazy() { 
   }
	
	// 公共获取实例方法(线程不安全)
	public static SingletonDemoInLazy getInstance() { 
   
		if(instance == null ) { 
    // 使用的时候加载
			instance = new SingletonDemoInLazy();
		}
		return instance;
	}
}

上面这种写法,是线程不安全的,但是可以做到延迟加载

下面是线程安全的懒汉模式:

public class SingletonDemoInLazy { 
   
	
	// 私有实例,初始化的时候不加载(延迟加载)
	private static SingletonDemoInLazy instance;
	
	// 私有构造
	private SingletonDemoInLazy() { 
   }
	
	// 公共获取实例方法(线程安全,调用效率低)
	public synchronized static SingletonDemoInLazy getInstance() { 
   
		if(instance == null ) { 
   
			instance = new SingletonDemoInLazy();
		}
		return instance;
	}
}

上面代码中,通过关键字synchronized声明公共的获取实例的方法getInstance(),可以确保线程安全,能做到延迟加载,但是效率不高

3.3 double-checked locking(双重检查锁)

public class SingletonDemoInDoubleCheckLock { 
   
	
	// 私有实例,volatile关键字,禁止指令重排。
	private volatile static SingletonDemoInDoubleCheckLock instance;
	
	// 私有构造
	private SingletonDemoInDoubleCheckLock() { 
   }
		
	// 公共获取实例方法(线程安全)
	public static SingletonDemoInDoubleCheckLock getInstance() { 
   
		if(instance == null ) { 
    // 一重检查
			synchronized (SingletonDemoInDoubleCheckLock.class) { 
   
				if(instance == null) { 
    // 二重检查
					instance = new SingletonDemoInDoubleCheckLock();
				}
			}
		}
		return instance;
	}
}

在加锁之前判断是否为空,可以确保 instance 不为空的情况下,不用加锁,可以直接返回。

加锁之后,还需要判断 instance 是否为空,是为了防止在多线程并发的情况下,会实例化多个对象。例如:线程 a 和线程 b 同时调用 getInstance 方法,假如同时判断 instance 都为空,这时会同时进行抢锁。假如线程 a 先抢到锁,开始执行 synchronized 关键字包含的代码,此时线程 b 处于等待状态。线程 a 创建完新实例了,释放锁了,此时线程 b 拿到锁,进入 synchronized 关键字包含的代码,如果没有再判断一次 instance 是否为空,则可能会重复创建实例。

双重检查锁

  • 双重判断,延迟加载;
  • 线程安全;
  • JDK 版本要求1.5起。

3.4 静态内部类

public class SingletonDemoInStaticInnerClass { 
   
	
	// 静态内部类
	private static class InnerClass{ 
   
		// 初始化实例
		private final static SingletonDemoInStaticInnerClass INSTANCE = new SingletonDemoInStaticInnerClass();
	}
	
	// 私有构造
	private SingletonDemoInStaticInnerClass() { 
   }
	
	// 公关获取实例方法(线程安全,延迟加载)
	public static SingletonDemoInStaticInnerClass getInstance() { 
   
		return InnerClass.INSTANCE;
	}
}

静态内部类

  • 利用了classloader机制来保证初始化 instance 时只有一个线程,线程安全
  • 只有通过显式调用 getInstance 方法时,才会显式装载静态内部类,从而实例化instance延迟加载

3.5 枚举

public enum SingletonEnum { 
   
	
	// 枚举元素本身就是单例 
	INSTANCE;
	
	
	// 其他要执行的方法
	public void sayHello() { 
   
		System.out.println("你好");
	}
	......
}

枚举:这是实现单例模式的最佳方法。它更简洁,不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。但是不是延迟加载的。

如何选用哪种方式实现单例模式?
一般情况下,不建议懒汉式,建议使用饿汉式;只有在要明确实现延迟加载效果时,才会使用静态内部类;如果涉及到反序列化创建对象时,可以尝试使用枚举;如果有其他特殊的需求,可以考虑使用双重检查锁。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/156328.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 完美解决pycharm安装第三方库失败的问题[通俗易懂]

    完美解决pycharm安装第三方库失败的问题[通俗易懂]使用pycharm安装第三库的流程1.点击wenjian1.

    2022年8月25日
    5
  • xml实用教程_如何学唱歌入门教程

    xml实用教程_如何学唱歌入门教程前言:“与其每天担心未来,不如努力现在。别对自己丧失信心,成长的路上,只有奋斗才能给你最大的安全感。”你好,我是梦阳辰!下面和我一起学习XML吧!01.XML概述1.什么是XML?XML(eXt

    2022年8月1日
    7
  • Redis的数据类型(四)—— Sortedset数据类型

    Redis的数据类型(四)—— Sortedset数据类型**Sortedset数据类型**一、redissortedset介绍在集合类型的基础上,有序集合类型为集合中的每个元素都关联一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在在集合中,还能够获得分数最高或最低的前N个元素、获取指定分数范围内的元素等与分数有关的操作。在某些方面有序集合和列表类型有些相似。1、二者都是有序的。2、二者都可以获得某一范围的元素。但是,二者…

    2022年10月20日
    3
  • vue 父子组件传值

    vue 父子组件传值父子组件传值最常用的方法(4种)1.父组件传递数据给子组件(props)vue官方文档特别注意!!特别注意!!特别注意!!props传值的类型如果是Object类型,如对象,数组等,传递过去的是地址,在子组件中修改这些数据,会连带的把父组件中的值一并修改,强烈建议在子组件中深拷贝之后再使用这些值。然后使用$emit的形式,将修改好的值再传递给父组件,这样数据就会以一种单向的,可预测的形式进行修改(如果不深拷贝,会造成数据被修改后,很难找到修改源头,非常恶心)父组件父组件,定义变量tes

    2022年5月17日
    52
  • c语言大数求和_指数幂的加法

    c语言大数求和_指数幂的加法文章目录1.大数加法2.大数幂运算3.大数求余废话不多说,直接上代码了。1.大数加法stringgetCountAdd(stringa,stringb){ stringc=””; intbit=-1;//判断是否进位-1为否,其他为进位数 inti=a.length()-1;//获得a字符串长度 intj=b.length()-1;//获得b字符串长度 //第一种情况两者都处理完 while(i!=-1&&j!

    2022年10月6日
    2
  • (转)[IDEA]如何安装codelf[通俗易懂]

    (转)[IDEA]如何安装codelf[通俗易懂]stormgolandpycharmwebstormIntelliJidea等)安装步骤直接浏览器访问http://unbug.github.io/codelf/JetBrainsIDE插件安装插件安装onlinesearch配置onlinesearch添加codelf搜索onlinesearch设置快捷键

    2022年5月4日
    135

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号