【Java基础】day16

news2024/11/18 5:41:28

day16

一、switch-case 和 if-else 谁更快?

switch-case

在 switch-case 中,case 的值是连续的话,会生成一个 TableSwitch 来进行优化,这样的情况下,只需要在表中进行判断即可。

这里使用 0-4 的连续值来进行测试

如果说多加几个 Case 的值,但是范围控制在比较小的范围时:

这里使用 0-9 之间的不连续的值来进行测试

可以发现仍然使用了一个 TableSwitch 来进行优化。继续加大范围,但是只有少数能使用到,可以看到:

这一部分使用了 LookupSwitch 来进行判断,可以很明显看出就是执行搜索指令来得到目标执行的代码块。那么在这种情况下,switch-case 的效率是比较低的。JVM 虚拟机规范中提到:

Compilation of switch statements uses the tableswitch and lookupswitch instructions. The tableswitch instruction is used when the cases of the switch can be efficiently represented as indices into a table of target offsets. The default target of the switch is used if the value of the expression of the switch falls outside the range of valid indices.Where the cases of the switch are sparse, the table representation of the tableswitch instruction becomes inefficient in terms of space. The lookupswitch instruction may be used instead.

The Java virtual machine specifies that the table of the lookupswitch instruction must be sorted by key so that implementations may use searches more efficient than a linear scan. Even so, the lookupswitch instruction must search its keys for a match rather than simply perform a bounds check and index into a table like tableswitch. Thus, a tableswitch instruction is probably more efficient than a lookupswitch where space considerations permit a choice.

这一部分的大意就是:

switch 语句的编译使用 tableswitch 和 lookupswitch 指令。当 switch 的情况可以有效地表示为目标偏移表中的索引时,使用 tableswitch 指令。如果 switch 表达式的值超出有效索引范围,则使用 switch 的默认目标。在 switch 的情况稀疏的情况下,tableswitch 的 table 表示在空间方面变得低效,也可以使用 lookupswitch 指令。

Java 虚拟机指定 lookupswitch 指令的表必须按键排序,以便实现可以使用比线性扫描更高效的搜索。即使如此,lookupswitch 指令也必须搜索其键以查找匹配项,而不是简单地执行边界检查并索引到类似 tableswitch 的表中。因此,在空间考虑允许选择的情况下,tableswitch 指令可能比 lookupswitch 更有效。

这个 tableswitch 相比 lookupswitch 的转换,什么时候才算是稀疏,这就取决于编译器的决策问题了。这一决策涉及到数理统计,找到效率的执行上的差异来决定。

如果使用的是其他的数据类型进行 switch 的判断时,可以看到这一优化仍然存在。

那么如果 case 之间使用的是不同数据类型呢?

使用 char 类型和 int 类型同时进行判断测试

结果仍然没有改变,最终的底层还是会使用的 ASCII 码进行相关计算。同理来说,如果是 String 类型,则同样换算数字来进行计算,只不过使用的是 String 的 hashcode 方法来获取 String 的对应数值。

if-else
仍然使用之前的测试方法,这里使用连续的小范围的值进行测试:

这里可以看到使用的是逐个进行判断的方式。再使用不连续的方式进行判断:

这里结论没有改变,仍然是使用的逐个判断。

结论

  • 只有在 case 中的条件是连续的或范围相隔不大时,编译器会使用表结构做优化,性能优于 if-else。
  • 其他情况下,switch-case 是逐个分支判断,性能与 if-else 无异。
  • switch-case 中的 case 只能是常量,而 if-else 用途更广一些。

在选择分支较多且连续或者范围相隔不大时,选用 switch-case 结构会提高程序的效率,但 switch 不足的地方在于只能处理字符或者数字类型的变量。if-else 结构更加灵活一些,if-else 结构可以用于判断表达式是否成立,应用范围更广,switch-case 结构在某些情况下可以替代 if-else 结构。

Website
https://godbolt.org/

二、枚举为什么是实现单例模式的最好方式?

这种方式是 Effective Java 作者 Josh Bloch 提倡的方式。它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。由于 JDK 1.5 之后才加入 enum 特性,这种方式在实际开发中用的比较少。

不能通过反射攻击 reflection attack 来调用私有构造方法,所以能绝对防止多次实例化。
枚举类型会在类加载阶段就进行初始化,类加载阶段是数据安全的,所以是线程安全的。

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。

public enum Singleton {  
    INSTANCE;
    public void whateverMethod() {  
    }  
}

三、为什么已经有了 synchronized 还需要 volatile?

从两个角度考虑:

  • 因为 synchronized 是一种锁机制,存在阻塞问题和性能问题;而 volatile 并不是锁,不存在阻塞和性能问题。
  • 因为 volatile 借助了内存屏障来帮助其解决可见性和有序性问题,在有些场景中是可以避免发生指令重排现象。

针对单线程,因为 as-if-serial 语义以及线程执行的时候需要遵守线程内语义 intra-thread semantics(保证重排序不会改变单线程内的程序执行结果),所以 synchronized 修饰的部分不会出现指令重排。那么 volatile 避免的指令重排是不是都可以通过 synchronized 解决呢?
并不是,参考 DCL 双重检查锁的实现。不使用 volatile 可能让其他线程访问到还未初始化完毕的对象,导致获取的对象为空,引发 NPE。

Java 对象在进行创建的时候,通常会有三个步骤:

  1. 分配对象内存
  2. 调用构造器方法,执行初始化
  3. 将对象引用赋值给变量

如果在不使用 volatile 关键字的时候,可能会发生指令重排,导致对象的初始化在多线程环境下的执行顺序发生变化。

指令重排:是机器指令级别的重排,并不是代码行级别的重排。

问题:

① 既然是加锁状态,那么同时刻只会有一个线程执行加锁代码块,且在执行同步块的时候,会刷新主内存,是否也能保证可见性?
是保证了可见性,但是因为指令重排序的问题,会有获取到空对象的风险。

② 在执行初始化的时候,是在同步代码块中的。初始化指令集执行时,是会保证原子性的,也就是只有一个线程能执行这一块代码。这个时候如果未初始化完毕,那么 singleton 对象仍为空。

此刻其余线程如果执行 DCL,会突破第一道检查,在代码块处等待锁的释放。在第一个执行完初始化的线程释放锁之后,其余线程执行代码块,会进行主内存的刷新,所以判断第二次,会得到完整的 singleton 对象。

这个过程中,如果不使用 volatile 修饰 singleton 对象,那么在初始化过程中,会因为指令重排序导致初始化过程出现问题。

指令重排可能导致对象在创建过程中先指向了引用,再执行初始化操作。这样违背了原本的初始化顺序,单线程环境下确实最终的结果不会发生改变。但是多线程下,其余线程在执行第一重检查的时候,可能获取到了只指向了引用还未初始化的空对象,这样就会存在 NPE 风险。

Reference
https://blog.csdn.net/bochuangli/article/details/122862651

四、Java 中的自旋锁怎么实现的?

自旋锁并不是真正的锁,而是让等待的线程先原地等待一下,通常实现方式很简单:

int SPIN_LOCK_NUM = 64;
int i = 0;
boolean wait = true;

do {
    // 尝试获取资源锁
    wait = xxx
} while (wait && (++i) < SPIN_LOCK_NUM);

在 Java 的 Unsafe 中的 CAS,就使用到了自旋锁。

public final class Unsafe {
    public final int getAndSetInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var4));
    
        return var5;
    }
}

根据自旋的一些概念,可以得知自旋锁是不能递归的,否则会自己获取自己已经拥有的锁,导致死锁的出现。

一个线程获取了一个自旋锁,在执行时被中断处理程序打断,因此该线程只是暂停执行,并未退出,仍持有自旋锁;而中断处理程序尝试获取自旋锁而获取不到,只能自旋;这就造成一个事实:中断处理子程序 ISR 拿不到自旋锁,导致自旋而无法退出,该线程被中断无法恢复执行至退出释放自旋锁,此时就造成了死锁,导致系统崩溃。

存在这样的问题时候,针对自旋锁,就需要有几个特性:

  • 在临界区持有自旋锁的线程不能休眠,休眠会引起进程切换,CPU 就会被另一个进程占用等无法使用;
  • 持有自旋锁的线程不允许被中断,哪怕是 ISR 也不行,否则就存在 ISR 自旋;
  • 持有自旋锁的线程,其内核不能被抢占,否则等同于 CPU 被抢占。

所以为了避免自旋发生死锁,我们需要针对自旋锁,添加限制条件:

  • 持有自旋锁的线程,不能因为任何原因而放弃 CPU;
  • 基于上述问题,自旋需要添加一个上限时间以防死锁。

Reference
https://juejin.cn/post/6930427187237486605

五、是否所有的 GC 都会触发 STW ?

所有的 GC 都会 STW ,只是存在时间长短区别。原因是可达性分析算法中枚举根节点 GC Roots 会导致所有 Java 执行线程停顿。STW 事件和采用哪款 GC 无关,所有的 GC 都有这个事件。只是现在的 GC 算法越来越精进,减少了 STW 的时间。

并不是只有 Full GC 才会触发 STW,还包括 SafePoint 安全点检查状态。Safepoint 可以理解成是在代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,线程可以暂停。在 SafePoint 保存了其他位置没有的一些当前线程的运行信息,供其他线程读取。可以理解为线程只有运行到了 SafePoint 的位置,它一切的状态信息才是确定的,也只有这个时候,才知道这个线程用了哪些内存。

Reference
https://zhuanlan.zhihu.com/p/437620682
https://www.zhihu.com/question/371699670
https://blog.csdn.net/m0_67401417/article/details/124277023

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/441986.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UE4: Niagara系统实现雨天效果,并跟随人物移动

目录 创建雨丝Niagara系统雨丝跟随人物移动 创建雨丝Niagara系统 1.创建一个Niagara系统 选择Fountain选项&#xff0c;点一下加号&#xff0c;再点完成&#xff1a; 命名一下打开&#xff1a; 先从粒子生成的方向入手&#xff0c;在Niagara的界面中删掉“Add Velocity in…

springboot+vue小区物业管理系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的小区物业管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风…

C11新特性之Lambda表达式

一.lambda语法 优点&#xff1a; 1.可以定义简短的函数。 2.使用lambda表达式使代码更紧凑&#xff0c;可读性更好。 语法&#xff1a; []表示不捕获任何变量[this]表示值传递方式捕捉当前的this指针 [&]表示引用传递方式捕捉所有父作用域的变量&#xff08;包括this&…

[架构之路-170]-《软考-系统分析师》-5-数据库系统-1-数据库模式、数据模型、数据库访问的标准接口

在当今的知识经济时代&#xff0c;信息是经济发展的战略资源&#xff0c;信息技术已经成为社会生产力中重要的组成部分。人们充分认识到&#xff0c;数据库是信息化社会中信息资源管理与开发利用的基础&#xff0c;当今的计算机信息系统也都以数据库技术为基础。对于一个国家来…

Linux DNS服务

DNS 作用 DNS是 域名系统 的英文缩写&#xff0c;作为将域名与IP地址相互映射的一个分布式数据库&#xff0c;让人可以通过域名访问互联网 正向解析 将域名解析为IP反向解析 根据IP查找对应域名 域名结构 http://www.sina.com.cn./ http://主机名.子域.二级域.顶级域.根域 / 每…

【Linux学习】信号——预备知识 | 信号产生 | 核心转储

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《Linux学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 信号 &#x1f514;信号&#x1f3b5;预备知识&#x1f3b5;信号处理方法的注册 &#x1f514;信…

C/C++每日一练(20230421)

目录 1. 位1的个数 &#x1f31f; 2. 递归和非递归求和 ※ 3. 俄罗斯套娃信封问题 &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 位1的…

大数据技术之Kafka——Kafka入门

目录 一、概述 1.1 为什么要有Kafka ​编辑 1.2 定义 1.3 消息队列 1&#xff09;消息队列的应用场景 2&#xff09;消息队列的两种模式 1.4 基础架构 二、Producer生产者 2.1 生产者消息发送流程 2.1.1 发送原理 2.2 异步发送API 2.2.1 普通异步发送 2.2.2 带回调…

第八章_Redis复制(replica)

是什么 官网地址 一句话 就是主从复制&#xff0c;master以写为主&#xff0c;Slave以读为主 当master数据变化的时候&#xff0c;自动将新的数据异步同步到其它slave数据库 能做什么 读写分离容灾恢复数据备份水平扩容支撑高并发 怎么使用 配从(库)不配主(库) 权限细节&am…

YOLOv8 更换主干网络之 MobileNeXt

《重新思考瓶颈结构以实现高效移动网络设计》 倒置残差块成为了移动网络架构设计的主流。它通过引入学习倒置残差和使用线性瓶颈的两个设计规则,改变了经典的残差瓶颈。在本文中,我们重新思考了这种设计改变的必要性,并发现它可能会带来信息丢失和梯度混淆的风险。因此,我们…

助力工业物联网,工业大数据之ODS层构建:代码结构及修改【九】

文章目录 01&#xff1a;ODS层构建&#xff1a;代码结构及修改02&#xff1a;ODS层构建&#xff1a;连接代码及测试03&#xff1a;ODS层构建&#xff1a;建库代码及测试04&#xff1a;ODS层构建&#xff1a;建表代码及测试 01&#xff1a;ODS层构建&#xff1a;代码结构及修改 …

漏扫常见(可验证)漏洞复现方法

SSL/TLS协议信息泄露漏洞(CVE-2016-2183) 检测到目标服务支持SSL弱加密算法 检测到目标服务支持SSL中等强度加密算法 nmap -sV -p 端口 --script ssl-enum-ciphers 目标ipSSH支持弱加密算法 nmap -sV -p 端口 --script ssh2-enum-algos 目标ipApache Tomcat示例脚本信息泄露…

YOLOv8 更换主干网络之 ShuffleNetv2

《ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design》 目前,神经网络架构设计多以计算复杂度的间接度量——FLOPs为指导。然而,直接的度量,如速度,也取决于其他因素,如内存访问成本和平台特性。因此,这项工作建议评估目标平台上的直接度量,而…

Vue 表单

文章目录 Vue 表单输入框复选框单选按钮select 列表修饰符.lazy.number.trim Vue 表单 这节为大家介绍 Vue.js 表单上的应用。 你可以用 v-model 指令在表单控件元素上创建双向数据绑定。 输入框 v-model 会根据控件类型自动选取正确的方法来更新元素。 输入框 实例中演示…

Maven(二)高级操作

目录 一、分模块开发与涉及&#xff08;重点&#xff09;1.工程模块划分2.modules_common拆分3.modules_pojo拆分4.modules_dao拆分5.modules_service拆分6.modules_controller拆分7.总结 二、聚合&#xff08;重点&#xff09;1.多模块构建维护2.聚合 三、继承&#xff08;重点…

HTML5 <param> 标签、HTML5 <progress> 标签

HTML5 <param> 标签 实例 HTML5 <param>标签为<object>标签提供嵌入内容的运行时参数的name与value对。请参考下述示例&#xff1a; 设置参数 "autoplay" 为 "true"&#xff0c;音频载入后会自动播放&#xff1a; <object data&qu…

基于AT89C51单片机的电子时钟设计

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/87695258?spm1001.2014.3001.5503 源码获取 主要内容&#xff1a; 1.设计出电子数字钟的电路&#xff0c;并用protus进行仿真画出对应的电路图 2.设计出电子数字钟…

2021地理设计组二等奖:基于GIS的东江源区土壤侵蚀及其影响因素空间分析

一、作品背景 水土保持情况普查对我国具有重要意义。我国目前是世界上水土流失最严重的国家之一&#xff0c;水土流失面积极其广且量大&#xff1b;严重的水土流失问题是我国生态环境问题的重要板块&#xff0c;若是持续恶化&#xff0c;将会严重影响我国的生态安全、饮水安全…

不得不说的创建型模式-抽象工厂模式

抽象工厂模式是一种创建型模式&#xff0c;它提供一个接口来创建一系列相关或相互依赖的对象&#xff0c;而不需要指定它们的具体类。这个接口被称为“抽象工厂”&#xff0c;它可以被不同的具体工厂实现来创建不同的产品族。 下面通过一个简单的示例来说明抽象工厂模式的底层原…

[API]string常量池string常用方法StringBuilder类(一)

String字符串类型&#xff1a; java.lang.String类使用final修饰&#xff0c;不能被继承 String的底层封装的是一个字符数组 String在内存中采用Unicode编码格式&#xff0c;每个字符占用2个字节的内存空间 字符串对象一旦创建&#xff0c;对象内容永远无法改变&#xff0c;…