来聊聊CAS
什么是CAS
CAS全称Compare-And-Swap,是一种无锁编程算法,即比较当前的值与旧值是否相等若相等则进行修改操作(乐观锁机制)
,该类常用于多线程共享变量的修改操作。而其底层实现也是基于硬件平台的汇编指令,JVM
只是封装其调用仅此而已。
CAS基础使用示例
如下所示,可以看出使用封装CAS
操作的AtomicInteger
操作多线程共享变量无需我们手动加锁,因为避免过多人为操作这就大大减少了多线程操作下的失误。
使用原子类操作共享数据
public class CasTest {private AtomicInteger count = new AtomicInteger();public void increment() {count.incrementAndGet();}// 使用 AtomicInteger 后,不需要加锁,也可以实现线程安全public int getCount() {return count.get();}public static void main(String[] args) {}
}
使用sync锁操作数据
public class Test {private int i=0;public synchronized int add(){return i++;}
}
CAS与传统synchronized区别
CAS为什么比synchronized快(重点)
CAS
工作原理是基于乐观锁
且操作是原子性的,与synchronized
的悲观锁(底层需要调用操作系统的mutex锁)
相比,效率也会相对高一些。
CAS是不是操作系统执行的?(重点)
不是,CAS
是主要是通过处理器的指令来保证原子性的。
CAS存在那些问题?
但即便如此CAS
仍然存在两个问题:
- 可能存在长时间
CAS
:如下代码所示,这就是AtomicInteger
底层的UNSAFE
类如何进行CAS
的具体代码 ,可以看出这个CAS
操作需要拿到volatile
变量后在进行循环CAS
才有可能成功这就很可能存在自旋循环,从而给CPU
带来很大的执行开销。
public final int getAndSetInt(Object var1, long var2, int var4) {int var5;//声明一个var5,只do {//通过getIntVolatile获取当前原子数的值var5 = this.getIntVolatile(var1, var2);//只有传入var1和现在获得的var5 一样,才说明值没被修改过,我们才能将值设置为var4} while(!this.compareAndSwapInt(var1, var2, var5, var4));return var5;}
-
CAS
只能对一个变量进行原子操作:为了解决这个问题,JDK 1.5
之后通过AtomicReference
使得变量可以封装成一个对象进行操作 -
ABA
问题:总所周知CAS就是比对当前值与旧值是否相等,在进行修改操作,假设我现在有一个变量值为A,我改为B,再还原为A,这样操作变量值是没变的?那么CAS也会成功不就不合理吗?这就好比一个银行储户想查询概念转账记录,如果转账一次记为1,如果按照ABA问题的逻辑,那么这个银行账户转账记录次数有可能会缺少。为了解决这个问题JDK 1.5提供了AtomicStampedReference
,通过比对版本号在进行CAS
操作,那么上述操作就会变为1A->2B->3A
,由于版本追加,那么我们就能捕捉到当前变量的变化了。
从源码角度了解java如何封装汇编的UNSAFE
代码也很简单,就是拿到具有可见性的volatile
变量i,然后判断i和当前对象paramObject
对应的i值是否一致,若一致则说明没被人该过,进而进行修改操作,反之自旋循环获取在进行CAS。
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt){int i;doi = getIntVolatile(paramObject, paramLong);while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));return i;}public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2){long l;dol = getLongVolatile(paramObject, paramLong1);while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));return l;}public final int getAndSetInt(Object paramObject, long paramLong, int paramInt){int i;doi = getIntVolatile(paramObject, paramLong);while (!compareAndSwapInt(paramObject, paramLong, i, paramInt));return i;}public final long getAndSetLong(Object paramObject, long paramLong1, long paramLong2){long l;dol = getLongVolatile(paramObject, paramLong1);while (!compareAndSwapLong(paramObject, paramLong1, l, paramLong2));return l;}public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2){Object localObject;dolocalObject = getObjectVolatile(paramObject1, paramLong);while (!compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2));return localObject;}
手写Unsafe实现20个线程500次CAS自增
代码逻辑和注释如下,读者可自行debug查看逻辑
public class CasCountInc {private static Logger logger = LoggerFactory.getLogger(CasCountInc.class);// 获取Unsafe对象private static Unsafe unsafe = getUnsafe();// 线程池数目private static final int THREAD_COUNT = 20;// 每个线程运行自增次数private static final int EVERY_THREAD_ADD_COUNT = 500;// 自增的count的值,volatile保证可见性private volatile int count = 0;// count字段的偏移量private static long countOffSet;private static Unsafe getUnsafe() {Unsafe unsafe = null;try {Field field = Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);unsafe = (Unsafe) field.get(null);} catch (Exception e) {logger.info("获取unsafe失败,失败原因:[{}]", e.getMessage(), e);}return unsafe;}static {try {countOffSet = unsafe.objectFieldOffset(CasCountInc.class.getDeclaredField("count"));} catch (NoSuchFieldException e) {logger.error("获取count的偏移量报错,错误原因:[{}]", e.getMessage(), e);}}public void inc() {int oldCount = 0;//基于cas完成自增do {oldCount = count;} while (!unsafe.compareAndSwapInt(this, countOffSet, oldCount, oldCount + 1));}public static void main(String[] args) throws InterruptedException {CasCountInc casCountInc = new CasCountInc();CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);IntStream.range(0, THREAD_COUNT).forEach(i -> {new Thread(() -> {IntStream.range(0, EVERY_THREAD_ADD_COUNT).forEach((j) -> {casCountInc.inc();});countDownLatch.countDown();}).start();});countDownLatch.await();logger.info("count最终结果为 [{}]", casCountInc.count);}
}
CAS 平时怎么用的,会有什么问题,为什么快,如果我用 for 循环代替 CAS 执行效率是一样的吗?(重点)
CAS
平时怎么用的:我们希望从一个视频中完成人像比对的功能,通过图像识别技术完成视频逐帧切割,只要有一帧的图片被识别分数达到90以上即可算完成。任务是串行的,如果是个大视频则执行时间会非常漫长。
经过对需求复盘,整体来说这个功能就是多任务只要有一个任务完成就算完成的需求。对此我们写了一个Callable
的任务,这个任务中的多线程共享两个变量atomicInteger
、countDownLatch
,都是由外部调度的,只要一个任务分数达到90则CAS
自增,countDown
倒计时门闩。
public class Task implements Callable<Integer> {private static Logger logger = LoggerFactory.getLogger(Task.class);private AtomicInteger atomicInteger ;private CountDownLatch countDownLatch;public Task(AtomicInteger atomicInteger, CountDownLatch countDownLatch) {this.atomicInteger = atomicInteger;this.countDownLatch = countDownLatch;}@Overridepublic Integer call() throws Exception {int score = (int) (Math.random() * 100);logger.info("当前线程:{},识别分数:{}", Thread.currentThread().getName(), score);synchronized (this){}if (score > 90 && atomicInteger.getAndIncrement()==0) {logger.info("当前线程:{} countDown",Thread.currentThread().getName());countDownLatch.countDown();logger.info("当前线程:{} 返回比对分数:{}", Thread.currentThread().getName(), score);return score;}return -1;}
}
执行代码
public class Main {private static Logger logger = LoggerFactory.getLogger(Main.class);public static void main(String[] args) throws InterruptedException {ExecutorService threadPool = Executors.newFixedThreadPool(100);CountDownLatch countDownLatch=new CountDownLatch(1);for (int i = 0; i < 100; i++) {Future<Integer> task = threadPool.submit(new Task(countDownLatch));}logger.info("阻塞中");countDownLatch.await();logger.info("阻塞结束");threadPool.shutdown();while (!threadPool.isTerminated()){}}
}
存在问题:CAS是基于乐观锁机制,所以数据同步失败就会原地自旋,在高并发场景下开销很大,所以线程数很大的情况下不建议使用原子类。
存在问题: 如果并发量大的话,自旋的线程多了就会导致性能瓶颈。
for 循环代替 CAS执行效率是否一样:大概率是CAS快,原因如下:
- CAS是
native
方法更接近底层 - for循环为了保证线程安全可能会用到
sync
锁或者Lock
无论那种都需要上锁和释放的逻辑,相比CAS乐观锁来说开销很大。
AtomicInteger以及相关原子类
原子类更新基本类型
AtomicBoolean: 原子更新布尔类型。
AtomicInteger: 原子更新整型。
AtomicLong: 原子更新长整型。
原子类更新数组类型
AtomicIntegerArray: 原子更新整型数组里的元素。
AtomicLongArray: 原子更新长整型数组里的元素。
AtomicReferenceArray: 原子更新引用类型数组里的元素。
基本使用示例
import java.util.concurrent.atomic.AtomicIntegerArray;public class AtomicIntegerArrayDemo {public static void main(String[] args) throws InterruptedException {AtomicIntegerArray array = new AtomicIntegerArray(new int[] { 0, 0 });System.out.println(array);
// 索引1位置+2System.out.println(array.getAndAdd(1, 2));System.out.println(array);}
}
原子类更新引用类型
AtomicReference: 原子更新引用类型。AtomicStampedReference: 原子更新引用类型, 内部使用Pair来存储元素值及其版本号。AtomicMarkableReferce: 原子更新带有标记位的引用类型。
原子类操作引用类型使用示例
import java.util.concurrent.atomic.AtomicReference;public class AtomicReferenceTest {public static void main(String[] args){// 创建两个Person对象,它们的id分别是101和102。Person p1 = new Person(101);Person p2 = new Person(102);// 新建AtomicReference对象,初始化它的值为p1对象AtomicReference ar = new AtomicReference(p1);// 通过CAS设置ar。如果ar的值为p1的话,则将其设置为p2。ar.compareAndSet(p1, p2);Person p3 = (Person)ar.get();System.out.println("p3 is "+p3);System.out.println("p3.equals(p1)="+p3.equals(p1));System.out.println("p3.equals(p2)="+p3.equals(p2));}
}class Person {volatile long id;public Person(long id) {this.id = id;}public String toString() {return "id:"+id;}
}
原子类更新字段类型
方法简介
AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。
AtomicLongFieldUpdater: 原子更新长整型字段的更新器。
AtomicStampedFieldUpdater: 原子更新带有版本号的引用类型。
AtomicReferenceFieldUpdater: 上面已经说过此处不在赘述。
原子类更新字段使用示例
如下所示,我们创建一个基础类DataDemo
,通过原子类CAS
操作字段值进行自增操作。
public class TestAtomicIntegerFieldUpdater {private static Logger logger = LoggerFactory.getLogger(TestAtomicIntegerFieldUpdater.class);public static void main(String[] args) {TestAtomicIntegerFieldUpdater tIA = new TestAtomicIntegerFieldUpdater();tIA.doIt();}/*** 返回需要更新的整型字段更新器** @param fieldName* @return*/public AtomicIntegerFieldUpdater<DataDemo> updater(String fieldName) {return AtomicIntegerFieldUpdater.newUpdater(DataDemo.class, fieldName);}public void doIt() {DataDemo data = new DataDemo();// 修改公共变量,返回更新前的旧值 0AtomicIntegerFieldUpdater<DataDemo> updater = updater("publicVar");int oldVal = updater.getAndIncrement(data);logger.info("publicVar 更新前的值[{}] 更新后的值 [{}]", oldVal, data.publicVar);// 更新保护级别的变量AtomicIntegerFieldUpdater<DataDemo> protectedVarUpdater = updater("protectedVar");int oldProtectedVar = protectedVarUpdater.getAndAdd(data, 2);logger.info("protectedVar 更新前的值[{}] 更新后的值 [{}]", oldProtectedVar, data.protectedVar);// logger.info("privateVar = "+updater("privateVar").getAndAdd(data,2)); 私有变量会报错/** 下面报异常:must be integer* */
// logger.info("integerVar = "+updater("integerVar").getAndIncrement(data));//logger.info("longVar = "+updater("longVar").getAndIncrement(data));}class DataDemo {// 公共且可见的publicVarpublic volatile int publicVar = 0;// 保护级别的protectedVarprotected volatile int protectedVar = 4;// 私有变量private volatile int privateVar = 5;// final 不可变量public final int finalVar = 11;public volatile Integer integerVar = 19;public volatile Long longVar = 18L;}}
通过上述代码我们可以总结出CAS字段必须符合以下要求:
1. 变量必须使用volatile保证可见性
2. 必须是当前对象可以访问到的类型才可进行操作‘
3. 只能是实例变量而不是类变量,即不可以有static修饰符
4. 包装类也不行
CAS的ABA问题
CAS更新前会检查值有没有变化,如果没有变化则认为没人修改过,在进行更新操作。这种情况下,若我们A值修改为B,B再还原为A。这种修改再还原的操作,CAS是无法感知是否变化的,这就是所谓的ABA问题。
AtomicStampedReference源码详解
源码如下所示,可以看到AtomicStampedReference
解决ABA问题的方式是基于当前修改操作的时间戳和元引用值是否一致,若一直则进行数据更新
public class AtomicStampedReference<V> {private static class Pair<T> {final T reference; //维护对象引用final int stamp; //用于标志版本private Pair(T reference, int stamp) {this.reference = reference;this.stamp = stamp;}static <T> Pair<T> of(T reference, int stamp) {return new Pair<T>(reference, stamp);}}private volatile Pair<V> pair;..../*** expectedReference :更新之前的原始引用值* newReference : 新值* expectedStamp : 预期时间戳* newStamp : 更新后的时间戳*/public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) {// 获取当前的(元素值,版本号)对Pair<V> current = pair;return// 引用没变expectedReference == current.reference &&// 版本号没变expectedStamp == current.stamp &&//可以看到这个括号里面用了一个短路运算如果当前版本与新值一样就说更新过,就不往下走CAS代码了((newReference == current.reference &&newStamp == current.stamp) ||// 构造新的Pair对象并CAS更新casPair(current, Pair.of(newReference, newStamp)));}private boolean casPair(Pair<V> cmp, Pair<V> val) {// 调用Unsafe的compareAndSwapObject()方法CAS更新pair的引用为新引用return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);}
AtomicStampedReference解决ABA问题示例
代码示例,我们下面就用other代码模拟干扰现场,如果other现场先进行CAS更新再还原操作,那么main线程的版本号就会过时,CAS就会操作失败
/*** ABA问题代码示例*/
public class AtomicStampedReferenceTest {private static AtomicStampedReference<Integer> atomicStampedRef =new AtomicStampedReference<>(1, 0);public static void main(String[] args) {Thread main = new Thread(() -> {System.out.println("操作线程" + Thread.currentThread() + ",初始值 a = " + atomicStampedRef.getReference());int stamp = atomicStampedRef.getStamp(); //获取当前标识别try {Thread.sleep(1000); //等待1秒 ,以便让干扰线程执行} catch (InterruptedException e) {e.printStackTrace();}boolean isCASSuccess = atomicStampedRef.compareAndSet(1, 2, stamp, stamp + 1); //此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败System.out.println("操作线程" + Thread.currentThread() + ",CAS操作结果: " + isCASSuccess);}, "主操作线程");Thread other = new Thread(() -> {Thread.yield(); // 确保thread-main 优先执行atomicStampedRef.compareAndSet(1, 2, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);System.out.println("操作线程" + Thread.currentThread() + ",【increment】 ,值 = " + atomicStampedRef.getReference());atomicStampedRef.compareAndSet(2, 1, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);System.out.println("操作线程" + Thread.currentThread() + ",【decrement】 ,值 = " + atomicStampedRef.getReference());}, "干扰线程");main.start();other.start();}}
AtomicMarkableReference解决对象ABA问题
AtomicMarkableReference
,它不是维护一个版本号,而是维护一个boolean类型的标记,标记对象是否有修改,从而解决ABA问题。
public boolean weakCompareAndSet(V expectedReference,V newReference,boolean expectedMark,boolean newMark) {return compareAndSet(expectedReference, newReference,expectedMark, newMark);}
AtomicInteger自增到10000后如何归零
AtomicInteger atomicInteger=new AtomicInteger(10000);
atomicInteger.compareAndSet(10000, 0);
参考文献
JUC原子类: CAS, Unsafe和原子类详解
深入理解高并发编程
相关文章:
来聊聊CAS
什么是CAS CAS全称Compare-And-Swap,是一种无锁编程算法,即比较当前的值与旧值是否相等若相等则进行修改操作(乐观锁机制),该类常用于多线程共享变量的修改操作。而其底层实现也是基于硬件平台的汇编指令,JVM只是封装其调用仅此而…...
【EventBus】EventBus源码浅析
二、EventBus源码解析 目录 1、EventBus的构造方法2、订阅者注册 2.1 订阅者方法的查找过程2.2 订阅者的注册过程1. subscriptionsByEventType 映射:2. typesBySubscriber 映射:2.3 总结订阅者的注册过程 3、事件的发送 3.1 使用Post提交事件3.2 使用p…...
Buck电源设计常见的一些问题(二)MOS管炸机问题
MOS管炸机问题 1.概述2.MOS管的相关参数3.过电压失效4.过电流失效5.静电放电和热失效1.概述 在我们做电源产品或者电机控制器时候,经常会坏MOS管。我相信90%以上的硬件工程师在职场生涯中都会遇到这类问题。然而这类问题也总是让人防不胜防。经常我们都会开玩笑的说,没烧过管…...
Javascript高频面试题
系列文章目录 文章目录 系列文章目录前言1.JavaScript常见数据类型null 和 undefind区别symbol(ES6新增)、bigInt(ES10新增) 2.JavaScript判断数据类型的方式3. 和 区别,分别在什么情况使用?4.变量声明 va…...
锁--07_2---- index merge(索引合并)引起的死锁
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 案例分析生产背景死锁日志表结构执行计划 EXPLAN为什么会用 index_merge(索引合并)为什么用了 index_merge就死锁了解决方案注:M…...
后端打印不了trace等级的日志?-SpringBoot日志打印-Slf4j
在调用log变量的方法来输出日志时,有以上5个级别对应的方法,从不太重要,到非常重要 调用不同的方法,就会输出不同级别的日志。 trace:跟踪信息debug:调试信息info:一般信息warn:警告…...
声明式编程Declarative Programming
接下来要介绍第五种编程范式 -- 声明式编程。分别从它的优缺点、案例分析和适用的编程语言这三个方面来介绍这个歌编程范式。 声明式编程是一种编程范式,其核心思想是通过描述问题的性质和约束,而不是通过描述解决问题的步骤来进行编程。这与命令式编程…...
人工智能与天文:技术前沿与未来展望
人工智能与天文:技术前沿与未来展望 一、引言 随着科技的飞速发展,人工智能(AI)在各个领域的应用越来越广泛。在天文领域,AI也发挥着越来越重要的作用。本文将探讨人工智能与天文学的结合,以及这种结合带…...
JeecgBoot 框架升级至 Spring Boot3 的实战步骤
JeecgBoot 框架升级 Spring Boot 3.1.5 步骤 JEECG官方推出SpringBoot3分支:https://github.com/jeecgboot/jeecg-boot/tree/springboot3 本次更新由于属于破坏式更新,有几个生态内的组件,无法进行找到平替或无法升级,目前尚不完…...
论文阅读——Semantic-SAM
Semantic-SAM可以做什么: 整合了七个数据集: 一般的分割数据集,目标级别分割数据集:MSCOCO, Objects365, ADE20k 部分分割数据集:PASCAL Part, PACO, PartImagenet, and SA-1B The datasets are SA-1B, COCO panopt…...
gitlab下载,离线安装
目录 1.下载 2.安装 3.配置 4.启动 5.登录 参考: 1.下载 根据服务器操作系统版本,下载对应的RPM包。 gitlab官网: The DevSecOps Platform | GitLab rpm包官网下载地址: gitlab/gitlab-ce - Results in gitlab/gitlab-ce 国内镜像地…...
【SpringBoot篇】Interceptor拦截器 | 拦截器和过滤器的区别
文章目录 🌹概念⭐作用 🎄快速入门⭐入门案例代码实现 🛸拦截路径🍔拦截器interceptor和过滤器filter的区别🎆登录校验 🌹概念 拦截器(Interceptor)是一种软件设计模式,…...
conan入门(三十六):在set_version方法中从pom.xml中读取版本号实现动态版本定义
一般情况下,我们通过self.version字段定义conan 包的版本号如下: class PkgConan(ConanFile):name "pkg"version "1.7.3"因为版本号是写死的,所以这种方式有局限性: 比如我的java项目中版本号是在pom.xml中…...
为什么 GAN 不好训练
为什么 GAN 不好训练?先看 GAN 的损失: 当生成器固定时,堆D(x)求导,推理得到(加号右边先对log求导,再对负项求导) 然后在面对最优Discriminator时,Generator的优化目标就变成了&…...
select、poll、epoll 区别有哪些
文章目录 select、poll、epoll 区别有哪些?select:poll:epoll: select、poll、epoll 区别有哪些? select: 它仅仅知道了,有 I/O 事件发生了,却并不知道是哪那几个流(可…...
大模型下开源文档解析工具总结及技术思考
1 基于文档解析工具的方法 pdf解析工具 导图一览: PyPDF2提取txt: import PyPDF2 def extract_text_from_pdf(pdf_path):with open(pdf_path, rb) as file:pdf_reader PyPDF2.PdfFileReader(file)num_pages pdf_reader.numPagestext ""f…...
【华为数据之道学习笔记】5-4 数据入湖方式
数据入湖遵循华为信息架构,以逻辑数据实体为粒度入湖,逻辑数据实体在首次入湖时应该考虑信息的完整性。原则上,一个逻辑数据实体的所有属性应该一次性进湖,避免一个逻辑实体多次入湖,增加入湖工作量。 数据入湖的方式…...
Vue3-03-reactive() 响应式基本使用
reactive() 的简介 reactive() 是vue3 中进行响应式状态声明的另一种方式; 但是,它只能声明 【对象类型】的响应式变量,【不支持声明基本数据类型】。reactive() 与 ref() 一样,都是深度响应式的,即对象嵌套属性发生了…...
OpenAI开源超级对齐方法:用GPT-2,监督、微调GPT-4
12月15日,OpenAI在官网公布了最新研究论文和开源项目——如何用小模型监督大模型,实现更好的新型对齐方法。 目前,大模型的主流对齐方法是RLHF(人类反馈强化学习)。但随着大模型朝着多模态、AGI发展,神经元…...
TeeChart.NET 2023.11.17 Crack
.NET 的 TeeChart 图表控件提供了一个出色的通用组件套件,可满足无数的图表需求,也针对重要的垂直领域,例如金融、科学和统计领域。 数据可视化 数十种完全可定制的交互式图表类型、地图和仪表指示器,以及完整的功能集,…...
计算机网络常见的缩写
计算机网络常见缩写 通讯控制处理机(Communication Control Processor)CCP 前端处理机(Front End Processor)FEP 开放系统互连参考模型 OSI/RM 开放数据库连接(Open Database Connectivity)ODBC 网络操作系…...
vue cli 脚手架之配置代理
方法二...
STM32启动流程详解(超全,startup_stm32xx.s分析)
单片机上电后执行的第一段代码 1.初始化堆栈指针 SP_initial_sp 2.初始化 PC 指针Reset_Handler 3.初始化中断向量表 4.配置系统时钟 5.调用 C 库函数_main 初始化用户堆栈,然后进入 main 函数。 在正式讲解之前,我们需要了解STM32的启动模式。 STM32的…...
小程序接口OK,桌面调试接口不行
手机小程序OK,桌面版出现问题; 环境:iis反向url的tomcat服务,提供接口。 该接口post了一个很大的数组,处理时间比较久。 1)桌面调试出现错误,提示 用apipost调用接口同样出错, 502 - Web 服务器在作为网关或代理服…...
【贪心】LeetCode-406. 根据身高重建队列
406. 根据身高重建队列。 假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。 请你重新…...
【C++11特性篇】C++11中新增的initializer_list——初始化的小利器
前言 大家好吖,欢迎来到 YY 滴C11系列 ,热烈欢迎! 本章主要内容面向接触过C的老铁 主要内容含: 欢迎订阅 YY滴C专栏!更多干货持续更新!以下是传送门! 目录 一.探究std::initializer_list是什么…...
springboot(ssm宠物美容机构CRM系统 宠物服务商城系统Java系统
springboot(ssm宠物美容机构CRM系统 客户关系管理系统Java系统 开发语言:Java 框架:ssm/springboot vue JDK版本:JDK1.8(或11) 服务器:tomcat 数据库:mysql 5.7(或8.0ÿ…...
LSTM 双向 Bi-LSTM
目录 一.Bi-LSTM介绍 二.Bi-LSTM结构 Bi-LSTM 代码实例 一.Bi-LSTM介绍 由于LSTM只能从序列里由前往后预测,为了既能够从前往后预测,也能从后往前预测,Bi-LSTM便被发明了出来。简单来说,BiLSTM就是由前向LSTM与后向LSTM组合而成。 二.Bi-LSTM结构 转自:...
2024测试开发面试题完整版本(附答案)
目录 1. 什么是软件测试, 谈谈你对软件测试的了解 2. 我看你简历上有写了解常见的开发模型和测试模型, 那你跟我讲一下敏捷模型 3. 我看你简历上还写了挺多开发技能的, 那你给我讲讲哈希表的实现流程 4. 谈一谈什么是线程安全问题, 如何解决 5. 既然你选择走测…...
MySQL作为服务端的配置过程与实际案例
MySQL是一款流行的关系型数据库管理系统,广泛应用于各种业务场景中。作为服务端,MySQL的配置过程对于数据库的性能、安全性和稳定性至关重要。本文将详细介绍MySQL作为服务端的配置过程,并通过一个实际案例进行举例说明。 一、MySQL服务端配…...
白云区网站建设公/seo网络营销案例分析
在外行人看来,IBM的在线量子体验工具所构建的线路,有点像是计算机科学入门课程。计算的基石——逻辑门,被排列在数字画布上,将输入转化为输出。 但因为这是量子线路,所以这些门修改的不是通常的二进制0或1位ÿ…...
什么网站可以自己接工程做预算/网站建设推广多少钱
数组小谈😁 庆哥: 嗨,小白,知道啥是数组吗?😎 小白: 你看你这话说的,数组那还不简单,学计算机的没有不知道数组的吧,我们刚开始接触C语言的时候就有数组啊,现在在学习java,也有数组啊,一般不就这样嘛😁 int[] array = new int[10]这就创建了一个长度为10的…...
广州建设交易中心网站/网站建设网站
一、XML约束之DDT 1. 概念 XML中所有标签是用户自行定义的,在某些程序软件中使用的配置文件中,读取指定内容。为了约束用户必须编写指定的标签内容,而使用约束DDT(当然目前常使用的是schema,因为功能更强灵活)。 这里引用两个概…...
wordpress文章末尾/广告推送平台
项目背景和意义 目的:随着中国经济改革的不断发展,大学生的就业形势越来越严峻,就业方向也越来越广泛,就业手段也越来越繁杂。但是随着网络在社会中的普及尤其是社会中的先进群体——大学生,根据网络的普遍性和便利性&…...
湖南做网站找谁/门户网站推广方案
领导终于把我们的老的显示器换成液晶了, 感谢党,感谢人民,感谢孙总。 呵呵,今年下半年业务有点起色, 老总要改善我们的环境了。 公司是在进步,个人的能力也 有 进步 。 但是 不 明显,没有 大踏步…...
天津建设监理协会网站/北京做网络优化的公司
(图中显示图片的 Item 是在Header、Foot View中的) 最近有一个需求, 需要在ListView 的 HeaderView中 添加多个View, 最后发现每个View中都有一条分割线。 效果如图中第一张图片! 本来以为只要设置android:headerDivi…...