设计模式:单例模式介绍及8种写法(饿汉式、懒汉式、Double-Check、静态内部类、枚举)
一、饿汉式(静态常量)
这种饿汉式的单例模式构造的步骤如下:
- 构造器私有化;(防止用new来得到对象实例)
- 类的内部创建对象;(因为1,所以2)
- 向外暴露一个静态的公共方法;(getInstance)
示例:
class Singleton{
//1私有化构造方法
private Singleton(){
}
//2创建对象实例
private final static Singleton instance = new Singleton();
//3对外提供公有静态方法
public static Singleton getInstance(){
return instance;
}
}
这样的话,获取对象就不能通过 new 的方式,而要通过 Singleton.getInstance();并且多次获取到的都是同一个对象。
使用静态常量的饿汉式写法实现的单例模式的优缺点:
优点:
简单,类装载的时候就完成了实例化,避免了多线程同步的问题。
缺点:
类装载的时候完成实例化,没有达到 Lazy Loading (懒加载)的效果,如果从始至终都没用过这个实例呢?那就会造成内存的浪费。(大多数的时候,调用getInstance方法然后类装载,是没问题的,但是导致类装载的原因有很多,可能有其他的方式或者静态方法导致类装载)
总结:
如果确定会用到他,这种写是没问题的,但是尽量避免内存浪费。
二、饿汉式(静态代码块)
和上一种用静态常量的方法类似,是把创建实例的过程放在静态代码块里。
class Singleton{
//1同样私有化构造方法
private Singleton(){
}
//2创建对象实例
private static Singleton instance;
//在静态代码块里进行单例对象的创建
static {
instance = new Singleton();
}
//3提供静态方法返回实例对象
public static Singleton getInstance() {
return instance;
}
}
优缺点:和上一种静态常量的方式一样;
原因:实现本来就是和上面的一样,因为类装载的时候一样马上会执行静态代码块中的代码。
三、懒汉式(线程不安全)
上面的两种饿汉式,都是一开始类加载的时候就创建了实例,可能会造成内存浪费。
懒汉式的写法如下:
class Singleton{
private static Singleton instance;
private Singleton(){
}
//提供静态公有方法,使用的时候才创建instance
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
也就是说,同样是 1) 私有构造器;2) 类的内部创建实例;3) 向外暴露获取实例方法。这三个步骤。
但是懒汉式的写法,将创建的代码放在了 getInstance 里,并且只有第一次的时候会创建,这样的话,类加载的过程就不会创建实例,同时也保证了创建只会有一次。
优点:
起到了Lazy Loading 的作用
缺点:
但是只能在单线程下使用。如果一个线程进入了 if 判断,但是没来得及向下执行的时候,另一个线程也通过了这个 if 语句,这时候就会产生多个实例,所以多线程环境下不能使用这种方式。
结论:
实际开发不要用这种方式。
四、懒汉式(线程安全,同步方法)
因为上面说了主要的问题,就在于 if 的执行可能不同步,所以解决的方式也很简单。
class Singleton{
private static Singleton instance;
private Singleton(){
}
//使用的时候才创建instance,同时加入synchronized同步代码,解决线程不安全问题
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
只要在获取实例的静态方法上加上 synchronized 关键字,同步机制放在getInstance方法层面,就 ok。
优点:
保留了单例的性质的情况下,解决了线程不安全的问题
缺点:
效率太差了,每个线程想要获得类的实例的时候都调用 getInstance 方法,就要进行同步。
然而这个方法本身执行一次实例化代码就够了,后面的想要获得实例,就应该直接 return ,而不是进行同步。
结论:
实际开发仍然不推荐。
五、懒汉式(同步代码块)
这种写法是基于对上一种的思考,既然在方法层面效率太差,那直接在实例化的语句上加 synchronized 来让他同步,是不是就能解决效率问题呢?
class Singleton{
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance == null){
synchronized( Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
事实上,这种方法,让 synchronized 关键字放入方法体里,又会导致可能别的线程同样进入 if 语句,回到了第三种的问题,所以来不及同步就会产生线程不安全的问题。
结论:不可用
六、 双重检查Double Check
使用 volatile 关键字,让修改值立即更新到主存,相当于轻量级的synchronized。
然后在下面的实例化过程里采用 double check,也就是两次判断。
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;
}
}
可以回想一下:
4 的懒汉式同步方法写法里,getInstance方法是用了synchronized修饰符,所以虽然解决了 lazy loading 的问题,线程也安全,但是同步起来会很慢。
而 5 的懒汉式同步代码块写法,将 synchronized 修饰符加到内部的代码块部分,又会导致线程安全直接失效,因为可能大家都同时进入了 getInstance 方法。
所以双检查的方法,仍然采用 5 的写法,将代码块用 synchronized 修饰符修饰,同时,在这个内部,再加上第二重检查,这样,线程安全的同时,保证了后面的线程会先进行 if 的判断而不进入代码块,这样就同时达到了效率的提升。
优点:
double-check是多线程开发里经常用到的,满足了我们需要的线程安全&&避免反复进行同步的效率差&&lazy loading。
结论:推荐使用。
七、静态内部类
静态内部类:用static修饰的内部类,称为静态内部类,完全属于外部类本身,不属于外部类某一个对象,外部类不可以定义为静态类,Java中静态类只有一种,那就是静态内部类。
class Singleton{
//构造器私有化
private Singleton(){
}
//一个静态内部类,里面有一个静态属性,就是实例
private static class SingletonInstance{
private static final Singleton instance = new Singleton();
}
//静态的公有方法
public static Singleton getInstance(){
return SingletonInstance.instance;
}
}
核心:
- 静态内部类在外部类装载的时候并不会执行,也就是满足了 lazy loading;
- 调用getInstance的时候会取属性,此时才加载静态内部类,而 jvm 底层的类装载机制是线程安全的,所以利用 jvm 达到了我们要的线程安全;
- 类的静态属性保证了实例化也只会进行一次,满足单例。
结论:推荐。
八、枚举
将单例的类写成枚举类型,直接只有一个Instance变量。
enum Singleton{
instance;
public void sayOk(){
System.out.println("ok");
}
}
调用的时候也不用new,直接用Singleton.instance,拿到这个属性。(一般INSTANCE写成大写)
优点:
满足单例模式要的特点,同时还能够避免反序列化重新创建新的对象。
这种方法是effective java作者提供的方式。
结论:推荐。
九、总结
单例模式使用的场景是:
需要频繁创建和销毁的对象、创建对象耗时过多或耗资源太多(重型对象)、工具类对象、频繁访问数据库或者文件的对象(数据源、session工厂等),都应用单例模式去实现。
因为单例模式保证了系统内存中只存在该类的一个对象,所以能节省资源,提高性能,那么对外来说,单例的类都不能再通过 new 去创建了,而是采用类提供的获取实例的方法。
上面的八种写法里面:饿汉式两种基本是一样的写法,懒汉式三种都有问题,以上物种的改进就是双重检查,另辟蹊径的是静态内部类和枚举。
所以,单例模式推荐的方式有四种:
- 饿汉式可用(虽然内存可能会浪费);
- 双重检查;
- 静态内部类;
- 枚举。
十、单例模式在JDK里的应用
Runtime类就是一个单例模式的类,并且可以看到,他是采用我们所说的第一种方式,即饿汉式(静态常量的方式)
- 私有构造器;
- 静态常量,类的内部直接将类实例化;
- 提供公有的静态方法。
设计模式:单例模式介绍及8种写法(饿汉式、懒汉式、Double-Check、静态内部类、枚举)的更多相关文章
- 设计模式-单例模式(Singleton Pattren)(饿汉模式和懒汉模式)
单例模式(Singleton Pattren):确保一个类在整个应用中只有一个实例,并提供一个全局访问点. 实现要点: 1. 私有化构造方法 2. 类的实例在类初始化的时候创建 3. 提供一个类方法, ...
- java 单利模式设计原理,饿汉式懒汉式,开发中推荐饿汉式
单例模式的设计: 1 //Single类进内存,对象还没有存在,只有调用了getInstance方法时,才建立对象. //对象是方法被调用时,才初始化,也叫做对象的延时加载.成为:懒汉式. //Si ...
- java基础10 单例模式之饿汉式和懒汉式单例
前言: 软件行业中有23中设计模式 单例模式 模版模式 装饰者模式 观察者模式 工厂模式 ........... 单例模式 1. 单例模式包括 1.1 饿汉式单例 1.2 ...
- java设计模式单例模式 ----懒汉式与饿汉式的区别
常用的五种单例模式实现方式 ——主要: 1.饿汉式(线程安全,调用率高,但是,不能延迟加载.) 2.懒汉式(线程安全,调用效率不高,可以延时加载.) ——其他: 1.双重检测锁式(由于JVM底层内部模 ...
- 牛客网Java刷题知识点之什么是单例模式?解决了什么问题?饿汉式单例(开发时常用)、懒汉式单例(面试时常用)、单例设计模式的内存图解
不多说,直接上干货! 什么是单例设计模式? 解决的问题:可以保证一个类在内存中的对象唯一性,必须对于多个程序使用同一个配置信息对象时,就需要保证该对象的唯一性. 如何保证? 1.不允许其他程序用new ...
- OOAD之单例模式Singleton的6种写法
1 主要作用是保证在Java应用程序中,一个类Class只有一个实例存在. 一 :第一种 饿汉式(预加载) public class Singleton { private Singleton(){ ...
- Java中单例设计模式,饿汉式和懒汉式
Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯 ...
- 设计模式——懒汉式单例类PK饿汉式单例类
前言 我们都知道生活中好多小软件,有的支持多IP在线,有的仅仅局限于单个IP在线.为什么这样设计,在软件开发阶段就是,有需求就是发展.这就是软件开发的一个设计模式--懒汉式单例类和饿汉式单例类. 内容 ...
- Java中如果把构造方法也私有化,如何创建对象?Java的单例设计模式——饿汉式和懒汉式区别
Java的单例模式——饿汉式 package com.swift; //Java单例设计模式——恶汉式 public class SinglePerson { private String name= ...
随机推荐
- Python Ethical Hacking - MAC Address & How to Change(3)
SIMPLE ALGORITHM Goal -> Check if MAC address was changed. Steps: 1. Execute and read ifconfig. ...
- node.js02 安装Node环境
安装Node环境 在node.js01中我大概了解了什么是node.js,这次进入起步阶段,首先要安装下Node环境. 开始安装 查看当前Node环境的版本号 win+r输入cmd进入命令行,输入no ...
- Python基础学习之环境搭建
Python如今成为零基础编程爱好者的首选学习语言,这和Python语言自身的强大功能和简单易学是分不开的.今天我们将带领Python零基础的初学者完成入门的第一步——环境搭建.本文会先来区分几个在P ...
- 关联吸纳的remote首次push报错rejected
F:\abb-iot\DmsAPI\DmsAPI (master -> origin) λ git push --set-upstream github master To github.com ...
- ElasticSearch(三)springboot整合ES
最基础的整合: 一.maven依赖 <parent> <groupId>org.springframework.boot</groupId> <artifac ...
- windows系统远程修改密码
1.需求:公司需要短时间.批量修改一些windows系统的管理员密码: 2.准备工作: a.下载软件:链接:https://pan.baidu.com/s/1kV52DqE1_4siPuxS5Mosc ...
- 如何用redis做缓存
redis缓存 在互联网应用中经常需要用redis来缓存热点数据. redis数据在内存,可以保证数据读取的高效,接近每秒数十万次的吞吐量 减少下层持久层数据库读取压力,像mongodb,每秒近千次读 ...
- 蒲公英 · JELLY技术周刊 Vol.16 谷歌首个线上 Web 开发者大会
蒲公英 · JELLY技术周刊 Vol.16 近期,谷歌有史以来的第一次线上谷歌 Web 开发者大会,Web Vitals.PWA.DevTools 和 Lighthouse 6.0 等一系列特性或产 ...
- pandas属性和方法
Series对象的常用属性和方法 loc[ ]和iloc[ ]格式示例表 Pandas提供的数据整理方法 Pandas分组对象的属性和方法 date_range函数的常用freq参数表
- 遍历多个 txt 文件进行获取值
import random def load_config(path): with open(path,'r') as tou: return [line for line in tou.readli ...