当前位置: 首页 > news >正文

【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题

  c96f743646e841f8bb30b2d242197f2f.gif

ddb5ae16fc92401ea95b48766cb03d96.jpeg692a78aa0ec843629a817408c97a8b84.gif


目录

1. 单例模式  

 (1) 饿汉模式  

 (2) 懒汉模式   

  1. 单线程版本  

  2. 多线程版本  

 2. 解决懒汉模式产生的线程安全问题   

  (1) 产生线程安全的原因  

  (2) 解决线程安全问题   

  1. 通过加锁让读写操作紧密执行  

方法一

方法二

  2. 处理加锁引入的新问题   

  问题描述   

  解决方法  

  3. 避免内存可见性&指令重排序  

  (1) 杜绝内存可见性问题   

  (2) 避免指令重排序问题  

1. 模拟编译器指令重排序情景

2. 指令重排序概述

3. 指令重排序类型

(1)编译器重排序

(2) 处理器重排序

4. 指令重排序所引发的问题


  1. 单例模式  

  • 单例模式能保证某个类在程序中,只存在唯 一 一 份实例,而不会创建出多个实例(不允许new多次)。
  • 要想保证单例模式只有唯一 一个实例,最重要的做法,就是用 private 修饰所有的构造方法;
  • 在 new 的过程中,会调用实例的类中的构造方法;
  • 只要用 private 修饰所有构造方法,在类外就无法获取到构造方法,进而使得 new 操作在编译时报错,因此保证某个类在程序中,只能有一份实例,而不能创建多个实例

  • 这一点在很多场景上都需要,比如 JDBC 中的 DataSource 实例就只需要一个.

单例模式具体的实现方式,分成 "饿汉" 和 "懒汉" 两种。


 (1) 饿汉模式  



下面这段代码,是对唯一成员 instance 进行初始化,用 static 修饰 instance,对 instance 的初始化,会在类加载的阶段触发;类加载往往就是在程序一启动就会触发;

由于是在类加载的阶段,就早早地创建好了实例(static修饰),这也就是“饿汉模式” 名字的由来。


在初始化好 instance 后,后续统一通过调用 getInstance() 方法获取 instance


单例模式的“点睛之笔”,用 private 修饰类中所有构造方法 



 (2) 懒汉模式   


  • 如果说,饿汉模式就是类加载的时候(一个比较早的时期)进行创建实例,并且使用private 修饰所有的构造方法,使得在代码中无法创建该类的其他实例
  • 那么懒汉方式的核心思路,就是延时的去创建实例,延时是真正用到的时候,再去创建。这样的思路,在编程中是非常有用的思路,一些情况下并不需要实例对象,通过懒汉模式来写代码,就不会去实例对象,进而可以减小开销,提升效率

  1. 单线程版本  

懒汉模式下,创建线程的时机,是在第一次使用的时候,而不是在程序启动的时候;

如果程序一启动,就要去使用实例,那 懒汉模式 和 饿汉模式 没有区别,但是程序运行很久了,都没有用到,此时懒汉模式创建实例的时间更晚一些,这样能减少不必要的开销


 2. 多线程版本  


 2.解决懒汉模式产生的线程安全问题   


  (1) 产生线程安全的原因  


  对于饿汉模式   

   对于懒汉模式   

为什么会有单线程版本和多线程版本的懒汉模式写法呢?我们来看单线程版本,如果运用到多线程的环境下,会出现什么问题:

  •  instance 被 static 修饰,多个线程调用 getInstance(),返回的是同一个内存变量;
  • 通过上面单线程版本的懒汉模式,我们可以发现,在 getInstance() 中,不但涉及了读操作,并且涉及了写操作;
  • 虽然对于图中标注的写操作,是赋值操作,并且这一步的操作是原子的,但是在多线程下调用的 getInstance() 方法,并不是原子的;
  • getInstance() 方法中,不但有写的操作,还有读的操作(满足条件才赋值,不满足条件不赋值),所以判断和赋值两个操作是紧密相连的,不能保证这两步操作紧密执行,就会出现下面的线程安全问题:

  • t1,t2如果按照上面的执行步骤,会出现值覆盖;随着第二个线程的覆盖操作,第一个线程 new 出来的对象会被 GC 回收掉。
  • 看起来没什么问题,但是 new 一个对象,会产生大量额外不必要的开销(new 一个对象的过程,可能会把大内存的数据,从硬盘加载到内存中);
  • 单例模式,不仅仅是期待只创建一个实例,更重要的是期望不要进行这种重复性的,耗时的工作,一来没意义,二来空间不允许;
  • 即使对于上面的情况,创建的第一个对象很快就被释放掉了,但是也是有数据加载过程的。

总结:

  • 饿汉模式只涉及对内存变量的读操作,不涉及写操作,因此饿汉模式是线程安全的,在单线程或者多线程的情况下,饿汉模式的基本形式不变;
  • 对于懒汉模式,在 getInstance() 中,涉及紧密相连的读写操作,但是因为读写操作不能紧密执行,导致出现线程安全问题。

  (2) 解决线程安全问题   


面试题:

这两个单例模式的 getInstance() 在多线程环境下调用,是否会出现 bug,如何解决 bug?


  1. 通过加锁让读写操作紧密执行  


对于上述饿汉模式出现线程安全问题的原因,就是读写操作(判断 + 赋值)不能紧密执行,因此,我们要对读写两步操作进行加锁,才能保证线程安全问题:

方法一

 这样加锁后,如果 t1 和 t2 还出现下图读写逻辑的执行顺序:

  • t2 会阻塞等待 t1 (或者 t1 会阻塞等待 t2)new好对象之后(读写操作结束后),释放锁,第二个线程才可以进行读写操作;
  • 此时第二个线程的判断,发现 instance != null,就会直接 return,而不会再进行实例 。

方法二

直接对 getInstance() 方法加锁,也能达到读写操作紧密执行的效果;

此时锁对象,locker  —> SingletonLazy.class,这两种方法达到的效果相同。


  2. 处理加锁引入的新问题   


  问题描述   

 

对于当前懒汉模式的代码,两个线程一把锁,是不会构成请求保持(形成死锁)的;

多个线程调用 getInstance() 方法,其实只需要保证第一个线程调用 getInstance(),执行的读写操作是紧密执行的即可,后续的线程在进行读操作发现 instance != null,就都不会触发写操作,自然就保证了线程安全;

但是按照上图的  getInstance() 方法,发现多个线程每次调用 getInstance() 都会进行一次加锁解锁操作,因为synchronized 是重量锁,多次的加锁解锁,会造成大量额外的开销,大大减低性能:

拓展:


StringBuffer 就是为了解决,大量拼接字符串时,产生很多中间对象问题而提供的一个类,提供 append insert 方法,可以将字符串添加到,已有序列的 末尾 或 指定位置。


StringBuffer 的本质是一个线程安全的可修改的字符序列,把所有修改数据的方法都加上了synchronized。但是保证了线程安全是需要性能的代价的。


在很多情况下我们的字符串拼接操作,不需要线程安全,这时候 StringBuilder 登场了,
StringBuilder 是 JDK1.5 发布的, StringBuilder  StringBuffer 本质上没什么区别,就是去掉了保证线程安全的那部分,减少了开销。所以在单线程情况下,优先考虑使用 StringBuilder


StringBuffer 和 StringBuilder 二者都继承了 AbstractStringBuilder底层都是利用可修改的 char数组 (JDK9以后是 byte 数组)。 


所以如果我们有大量的字符串拼接,如果能预知大小的话最好在new StringBuffer 或者  new StringBuilder 的时候设置好 capacity ,避免多次扩容的开销(扩容要抛弃原有数组,还要进行数组拷贝创建新的数组)。


  解决方法  

 再嵌套一次判断操作,既可以保证线程安全,又可以避免大量加锁解锁产生的开销:

  • 在单线程中,连续嵌套两层相同的 if 语句是没有意义的;因为单线程的 “执行流” 只有一个 ,嵌套两层相同的 if 语句结果相同;
  • 但是在多线程中,有多个并发执行的执行流,可能因为其中一个线程修改了 instance,导致其他线程再次执行到判断操作时,会有所不同;如上述懒汉模式,在多线程下,两个 if 得到的结果是不同的;
  • 虽然两个if相同,但是目的和作用截然不同;上面的 if,是用来判断是否需要加锁,下面的 if 判断是否需要new对象。

  • 虽然两个 if 相同,但是这只是一个巧和


  3. 避免内存可见性&指令重排序  


要杜绝可能会出现的内存可见性问题 ,并且避免指令重排序问题,只需要使用 volatile 修饰instance 即可:


  (1) 杜绝内存可见性问题   

假如不加volatile ,那么假设两个线程都执行到 synchronized 锁时, 一个线程加锁,另一个线程阻塞等待; 然后获取到锁对象的线程, 创建出来了这个单例;


释放锁之后,另一个没获取到锁对象的线程, 获取锁之后,执行 if 判断,结果它读取的到的instance的值,是之前寄存器缓存中的值,而寄存器中缓存的 instance 还是null,因此第二个线程又回去执行锁中的逻辑,就又会去实例化一个新的 instance。


内存可见性就是保证, 每次去读取的时候,  读取到的值都是最新的值(内存中的值),而不是之前缓存在寄存器中的值;


如果不加volatile ,在上面说的案例中, 会有可能存在第二个线程获取到锁对象,结果发现这个单例(instance)是等于 null的情况;所以需要加上volatile 来保证不会出现这样的情况。


  (2) 避免指令重排序问题  

  1. 模拟编译器指令重排序情景   

要在超市中买到左边购物清单的物品,有两种买法 

 方法一:根据购物清单的顺序买;(按照程序员编写的代码顺序进行编译)

 方法二:根据物品最近距离购买;(通过指令重排序后再编译)

两种方法都能买到购物清单的所有物品,但是比起第一种方法,第二种方法在不改变原有逻辑的情况下,优化执行指令顺序,更高效地执行完所有的指令。


处理好加锁所引入的问题之后,还有剩余的 指令重排序问题 和 可能会出现的 内存可见性 问题:

在第一个 if 结束后,可能不会直接 return,而是还有后续的逻辑;


如果是在最开始的懒汉模式的版本:

只有一个套着 synchronized 的 if 的时候,会因为加锁阻塞,而避免使用未触发对象 (instance未初始化) 的情况,就不会出现指令重排序问题;


但是我们为了考虑效率,为了减少不必要的加锁操作,减少开销,我们多加了一层 if,正是多加了一层if,使得 t2 线程因为未触发 synchronized ,而不会进入阻塞等待;

 


所以在 t1 线程还没来得及初始化 instance 时,t2 就直接拿着未初始化的 instance 执行第一个 if 后面,后续的逻辑了

左边是 多线程版本 的 懒汉模式 中的 getInstance() 要执行的操作,右边是多个线程调用 getInstance();

如果真的因为指令重排序,而导致 内存空间的首地址,赋值给引用变量(拿到钥匙) 的操作,被重排序到 初始化 instance 之前(装修),就会出现下面的问题:


在一个线程已经获取锁对象,进行加锁,还未来得及初始化 instance 时,另一个线程会因为最外层的 if 语句,而跳过 if 中代码块里的加锁操作,而避免了阻塞等待;


如果后续的 getInstance() 在第一层 if 语句后,还有其他逻辑,第二个线程就会拿着未被初始化的 instance 来进行后面的逻辑。


这就是指令重排序问题,而使用 volatile 修饰 instance 后,不但能确保每次读取操作,都是读内存,而且关于该变量的读取和修改操作,不会触发重排序


  2. 指令重排序概述  

指令重排序是指编译器或处理器为了提高性能,在不改变程序执行结果的前提下,可以对指令序列进行重新排序的优化技术。这种优化技术可以使得计算机在执行指令时更高效地利用计算资源,提高程序的执行效率。

JMM(Java 内存模型)详解


   指令重排序   


为了提升执行速度/性能,计算机在执行程序代码的时候,会对指令进行重排序。


   什么是指令重排序?   


简单来说就是系统在执行代码的时候并不一定是按照你写的代码的顺序依次执行。


   常见的指令重排序有下面2种情况:   


  (1) 编译器优化重排:   


编译器(包括JVM、JIT编译器等)在不改变单线程程序语义的前提下,重新安排语句的执行顺序。


   (2) 指令并行重排:   


现代处理器采用了指令级并行技术(Instruction-Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。


另外,内存系统也会有“重排序”,但又不是真正意义上的重排序。在JMM里表现为主存和本地内存的内容可能不一致,进而导致程序在多线程下执行可能出现问题。


Java 源代码会经历编译器优化重排—>指令并行重排一>内存系统重排的过程,最终才变成操作系统可执行的指令序列。


指令重排序可以保证串行语义一致,但是没有义务保证多线程间的语义也一致,所以在多线程下,指令重排序可能会导致一些问题。


对于编译器优化重排,和处理器的指令重排序(指令并行重排和内存系统重排都属于是处理器级别的指令重排序),处理该问题的方式不一样。


  • 对于编译器,通过禁止特定类型的编译器重排序的方式来禁止重排序。
  • 对于处理器,通过插入内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)的方式来禁止特定类型的处理器重排序。

内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用来禁止处理器指令发生重排序(像屏障一样),从而保障指令执行的有序性。


另外,为了达到屏障的效果,它也会使处理器写入、读取值之前,将主内存的值写入高速缓存,清空无效队列,从而保障变量的可见性。


  3. 指令重排序类型  

   (1)编译器重排序   

编译器在生成目标代码时会对源代码中的指令进行优化和重排,以提高程序的执行效率。编译器重排序时在编译阶段完成的,目的是生成更高效率的机器代码。


   (2) 处理器重排序   

处理器在执行指令也可以对指令进行重排序,以最大程度地利用处理器的流水线和多核等特性。目的提高指令的执行效率。


   4. 指令重排序所引发的问题   

虽然指令重排序可以提高程序的执行效率,但是在多线程编程中可能会引发内存可见性问题。由于指令重排序 可能导致共享变量的读写顺序,与代码中的顺序不一致,当多个线程同时访问共享变量时,可能会出现数据不一致的情况。


  c96f743646e841f8bb30b2d242197f2f.gif

692a78aa0ec843629a817408c97a8b84.gif

相关文章:

【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题

目录 1. 单例模式 (1) 饿汉模式 (2) 懒汉模式 1. 单线程版本 2. 多线程版本 2. 解决懒汉模式产生的线程安全问题 (1) 产生线程安全的原因 (2) 解决线程安全问题 1. 通过加锁让读写操作紧密执行 方法一 方法二 2. 处理加锁引入的新问题 问题描述 …...

MySQL电商多级分类表设计方案对比

MySQL电商多级分类表设计方案对比 在电商系统中,多级分类是一个常见的需求,用于组织和管理商品类别,合理的设计可以提高系统的性能和可维护性。本文将详细介绍三种不同的多级分类表设计方案,我们将使用宠物分类作为示例数据&…...

网络安全工程师需要知道哪些IPSec的基本原理?

IPSec是一种端到端的安全协议,为IP数据包提供认证、完整性和加密服务。它通过在IP层实现安全功能,确保数据在传输过程中的机密性、完整性和真实性。IPSec广泛应用于VPN、远程访问和企业内部网络通信等领域,是保护互联网通信安全的重要手段。 …...

leetcode 148. 排序链表 中等

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 示例 1: 输入:head [4,2,1,3] 输出:[1,2,3,4] 示例 2: 输入:head [-1,5,3,4,0] 输出:[-1,0,3,4,5]示例 3: …...

动态规划与贪心算法:核心区别与实例分析

动态规划与贪心算法:核心区别与实例分析 动态规划和贪心算法是计算机科学中用于解决优化问题的两种著名方法。它们各自的思路和应用场景有显著的区别,理解这些区别对解决相关问题至关重要。本文将详细探讨这两种算法的最优子结构、解法策略、适用场景&a…...

.NET 公共语言运行时(Common Language Runtime,CLR)

.NET 的公共语言运行时(Common Language Runtime,CLR)是 .NET Framework 和 .NET Core 的核心组件,负责运行和管理 .NET 程序。CLR 提供了一个高效、安全和稳定的执行环境,支持多种编程语言并处理各种系统级的任务。下…...

SpringBoot使用TraceId日志链路追踪

项目场景: 有时候一个业务调用链场景,很长,调了各种各样的方法,看日志的时候,各个接口的日志穿插,确实让人头大。为了解决这个痛点,就使用了TraceId,根据TraceId关键字进入服务器查询…...

YOLO11 旋转目标检测 | OBB定向检测 | ONNX模型推理 | 旋转NMS

本文分享YOLO11中,从xxx.pt权重文件转为.onnx文件,然后使用.onnx文件,进行旋转目标检测任务的模型推理。 用ONNX模型推理,便于算法到开发板或芯片的部署。 本文提供源代码,支持不同尺寸图片输入、支持旋转NMS过滤重复…...

PCL 点云拟合 拟合空间直线

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 设置RANSAC算法参数 2.1.2拟合直线模型 2.1.3 提取拟合直线内点 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接: PCL点云算法与项目实战案例汇总(长期更…...

我的创作纪念日-20241112-感谢困难

我的创作纪念日-20241112-感谢困难 一、机缘二、收获1、积累2、感谢困难 三、日常四、成就五、憧憬 一、机缘 我之前有一个自己的私人博客,但是后来发现CSDN的功能更强大,更专业,所以我就把自己博客内容转到CSDN上面来了。 二、收获 1、积累…...

苍穹外卖05-Redis相关知识点

目录 什么是Redis? redis中的一些常用指令 value的5种常用数据类型 各种数据类型的特点 Redis中数据操作的常用命令 字符串类型常用命令: 哈希类型常用命令 列表操作命令 集合操作命令 有序集合操作命令 通用命令 在java中操作Redis 环境…...

unity 玩家和炸弹切线计算方式

脚本挂在炸弹上! using System.Collections; using System.Collections.Generic; using UnityEngine;public class TargetDetaction : MonoBehaviour {private Transform PlayerTF;private Transform bomb;private float radius;private string Player "Play…...

【MySQL】MySQL中的函数之REGEXP_LIKE

在 MySQL 中,REGEXP_LIKE() 函数用于检查一个字符串是否与正则表达式匹配。不过需要注意的是,REGEXP_LIKE() 并不是所有版本的 MySQL 都支持的函数。这个函数是在 MySQL 8.0 版本中引入的。 基本语法 REGEXP_LIKE(str, pat [, match_type ])str: 要测试…...

跟着尚硅谷学vue2—进阶版4.0—Vuex1.0

5. Vuex 1. 理解 Vuex 1. 多组件共享数据-全局事件总线实现 红线是读&#xff0c;绿线是写 2. 多组件共享数据-vuex实现 vuex 不属于任何组件 3. 求和案例-纯vue版 核心代码 1.Count.vue <template><div><h1>当前求和为&#xff1a;{{ sum }}</h1&…...

深度学习服务器租赁AutoDL

省钱绝招 #AutoDL #GPU #租显卡...

excel常用技能

1.基础技能 1.1 下拉框设置 a. 选中需要设置的列或单元格&#xff0c;数据 ---》 数据验证 b.验证条件 ---> 序列&#xff08;多个值逗号隔开&#xff09; 1.2 进度条百分比显示设置 开始 ---> 条件格式 --->新建规则--->编辑规则 1.3 相对引用和绝对引用…...

Mac电脑中隐藏文件(即以 . 开头的文件/文件夹)的显示和隐藏的两种方法

方法一&#xff1a;使用电脑快捷键&#xff0c;步骤如下&#xff1a; 1、点击一下桌面&#xff0c;用来激活 Finder &#xff1b; 2、同时按下 Command Shift 点&#xff0c;即 【Command Shift . 】&#xff1b; 3、 打开可能包含此类文件的文件夹&#xff0c;比如磁盘…...

【Linux】:进程信号(信号概念 信号处理 信号产生)

✨ 眼里有诗&#xff0c;自向远方 &#x1f30f; &#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;Linux—登神长阶 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#…...

Flink运行时架构以及核心概念

1.运行构架 1.提交作业后启动一个客户端进程&#xff0c;客户端解析参数&#xff08;-d -t 等等&#xff09;&#xff0c;后进行封装由Actor通信系统提交&#xff0c;取消&#xff0c;更新任务给JobManager。 2.JobManager&#xff08;进程&#xff09;通信系统一个组件叫分发…...

用 Python 从零开始创建神经网络(五):损失函数(Loss Functions)计算网络误差

用损失函数&#xff08;Loss Functions&#xff09;计算网络误差 引言1. 分类交叉熵损失&#xff08;Categorical Cross-Entropy Loss&#xff09;2. 分类交叉熵损失类&#xff08;The Categorical Cross-Entropy Loss Class&#xff09;展示到目前为止的所有代码3. 准确率计算…...

电镀机的阳极是什么材质?

知识星球&#xff08;星球名&#xff1a;芯片制造与封测技术社区&#xff0c;点击加入&#xff09;里的学员问&#xff1a;电镀的阳极有什么讲究&#xff1f;什么是可溶性阳极和非可溶性阳极&#xff1f; 什么是可溶性阳极与非可溶性阳极&#xff1f; 可溶性阳极 阳极本身就是…...

Go 语言中的 make 函数详解

Go 语言中的 make 函数详解 make 是 Go 语言中的一个​​内置函数​​&#xff0c;用于​​初始化切片&#xff08;slice&#xff09;、映射&#xff08;map&#xff09;和通道&#xff08;channel&#xff09;​​这些引用类型。这些类型必须在使用前通过 make 初始化&#x…...

WaytoAGI东京大会开启AI全球化新对话:技术无国界,合作促创新

全球AI专家齐聚东京&#xff0c;一场关于技术无国界的对话正在进行。 2025年6月7日&#xff0c;一场备受瞩目的AI盛会——“WaytoAGI全球AI大会东京站”在日本东京樱美林大学新宿校区正式拉开帷幕。这场为期两天的会议&#xff08;6月7日至8日&#xff09;由国内最大的AI开源知…...

9.axios底层原理,和promise的对比(2)

&#x1f63a;&#x1f63a;&#x1f63a; 和promise的对比 完全可以直接使用 Promise 来发 HTTP 请求&#xff0c;比如用原生 fetch Promise 就可以实现网络请求功能&#x1f447; ✅ 用 Promise fetch 的写法&#xff08;原生&#xff09; fetch(‘https://api.example.c…...

【LeetCode】3170. 删除星号以后字典序最小的字符串(贪心 | 优先队列)

LeetCode 3170. 删除星号以后字典序最小的字符串&#xff08;中等&#xff09; 题目描述解题思路java代码 题目描述 题目链接&#xff1a;3170. 删除星号以后字典序最小的字符串 给你一个字符串 s 。它可能包含任意数量的 * 字符。你的任务是删除所有的 * 字符。 当字符串还…...

AI生成的基于html+marked.js实现的Markdown转html工具,离线使用,可实时预览 [

有一个markdown格式的文档&#xff0c;手头只有notepad的MarkdownPanel插件可以预览&#xff0c;但是只能预览&#xff0c;不能直接转换为html文件下载&#xff0c;直接复制预览的内效果又不太好&#xff0c;度娘也能找到很多工具&#xff0c;但是都需要在线使用。所以考虑用AI…...

青少年编程与数学 01-011 系统软件简介 07 iOS操作系统

青少年编程与数学 01-011 系统软件简介 07 iOS操作系统 一、发展历程&#xff08;一&#xff09;诞生初期&#xff08;2007 - 2008年&#xff09;&#xff08;二&#xff09;功能拓展与升级&#xff08;2009 - 2013年&#xff09;&#xff08;三&#xff09;持续优化与创新&…...

go中的接口返回设计思想

go中的接口返回设计思想 前言 在学习AI编码过程中&#xff0c;产生了类似以下结构的代码 &#xff1a; type MQClient interface {PublishMessage(queue string, message interface{}) error...... } ... type RabbitMQClient struct {conn *amqp.Connectionchannel *amqp.C…...

【方案分享】蓝牙Beacon定位精度优化(包含KF、EKF与UKF卡尔曼滤波算法详解)

蓝牙Beacon定位精度优化&#xff1a;KF、EKF与UKF卡尔曼滤波算法详解 标签&#xff1a;蓝牙定位&#xff5c;Beacon&#xff5c;卡尔曼滤波&#xff5c;UKF&#xff5c;EKF&#xff5c;RSSI&#xff5c;室内定位&#xff5c;滤波算法&#xff5c;精度优化 相关分享&#xff1a;…...

AndroidR车机TextToSpeech音频焦点异常问题分析

一、引言 文章《Android车机之TextToSpeech》介绍了TextToSpeech的使用,当前较多座舱系统语音服务都接入了原生TextToSpeech接口调用。 我司自研语音TTS服务,也接入了此TTS接口调用,对外提供TextToSpeech能力,播报时由客户端Client自行管理音频焦点,播报前申请音频焦点,…...