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

【JavaEE】【多线程】进阶知识

目录

  • 一、常见的锁策略
    • 1.1 悲观锁 vs 乐观锁
    • 1.2 重量级锁 vs 轻量级锁
    • 1.3 挂起等待锁 vs 自旋锁
    • 1.4 普通互斥锁 vs 读写锁
    • 1.5 可重入锁 vs 不可重入锁
    • 1.6 不公平锁 vs 公平锁
  • 二、synchronized特性
    • 2.1 synchronized的锁策略
    • 2.2 synchronized加锁过程
    • 2.3 其它优化措施
  • 三、CAS
    • 3.1 CAS概念
    • 3.2 CAS应用场景
      • 3.2.1 实现原子类。
      • 3.2.2 实现自旋锁
    • 3.3 CAS的ABA问题
      • 3.3.1 ABA问题简介
      • 3.3.2 解决方案
  • 四、JUC组件
    • 4.1 Callable接口
    • 4.2 ReentrantLock类
    • 4.3 Semaphore类
    • 4.4 CountDownLatch类

一、常见的锁策略

1.1 悲观锁 vs 乐观锁

悲观和乐观是指锁竞争的激烈情况。

  1. 悲观锁:加锁的时候预测接下来的锁竞争会非常激烈,就需要针对这样的激烈情况额外做工作;
  2. 乐观锁:加锁的时候预测接下来的锁竞争不激烈,就不需要额外做工作去处理锁竞争情况;

1.2 重量级锁 vs 轻量级锁

这两种锁就对应上诉的悲观乐观情况下的处理机制。

  1. 重量级锁:应对上面的锁竞争激烈的悲观情况,效率更低;
  2. 轻量级锁:应对上面的锁竞争不激烈的情况,效率更高。

1.3 挂起等待锁 vs 自旋锁

这又是对上面的重量级与轻量级锁的典型实现。

  1. 挂起等待锁:重量级锁的典型实现,是操作系统内核级别的,加锁发生竞争,线程进入阻塞后,就需要内核进行唤醒。获取锁的周期会变长,但是这期间不会消耗CPU资源。
  2. 自旋锁:轻量级锁的典型实现,是应用程序级别的,加锁时发生竞争,一般不进行阻塞,而是通过忙等,等待后续程序唤醒。获取锁的周期很短,可以及时获取到锁,但是这期间会一直消耗CPU资源。

1.4 普通互斥锁 vs 读写锁

这两种锁是针对加锁解锁时的线程安全问题。

  • 普通互斥锁只有加锁,解锁操作,并且读操作不会出现线程安全问题;
  • 而读写锁有读加锁,写加锁,和解锁操作,
    读锁与读锁之间不互斥;
    读锁与写锁之间存在互斥;
    写锁与写锁之间也存在互斥。
    读写锁主要是针对读操作多,写操作少的情况服务。

1.5 可重入锁 vs 不可重入锁

这组锁就是针对同一个线程多次嵌套获取同一把锁。

  • 可重入锁:字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。
  • 不可重入锁:字面意思是“不可以重新进入的锁”,即同一个线程多次获取同一把锁,会报错。

1.6 不公平锁 vs 公平锁

这组锁是针对线程获取锁的概率设置的。

  • 不公平锁:在Java中不公平锁是概率均等的随机分配锁;
  • 公平锁:在Java中公平锁是按照等待的时间先来后到分配锁。因为操作系统是随机调度,为了实现公平锁就需要数据结构来记录先后调度顺序。

二、synchronized特性

2.1 synchronized的锁策略

锁策略如下:

  • synchronized是一个自适应的锁,开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁。
  • 开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁。
  • 是一种普通互斥锁。
  • 是一种不公平锁。
  • 是一种可重入锁。

2.2 synchronized加锁过程

synchronized的自适应过程称为锁升级:无锁 -> 偏向锁 -> 自旋锁 -> 重量级锁。

  • 偏向锁:偏向锁不是真的 “加锁”, 只是给对象头中做一个 “偏向锁的标记”, 记录这个锁属于哪个线程.
    如果后续没有其他线程来竞争该锁, 那么就不用进行其他同步操作了(避免了加锁解锁的开销)
    如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了, 很容易识别
    当前申请锁的线程是不是之前记录的线程), 那就取消原来的偏向锁状态, 进入一般的轻量级锁状态.
    就像跟你搞暧昧,但是不正式跟你确认关系,如果没人来追求你就一直不确定关系,如果有人追求你就确认关系。

  • 无锁 -> 偏向锁 :这个阶段是进入synchronized代码块;

  • 偏向锁 -> 自旋锁:这个阶段是拿到偏向锁后,遇到其他线程来竞争这个锁;

  • 自旋锁 -> 重量级锁:这个阶段是JVM发现当前的锁竞争非常激烈。

2.3 其它优化措施

除了上面的synchronized锁升级以外,还有以下的优化措施:

  • 锁消除:编译器优化措施,编译器会对加锁的代码进行判断(但这种判断是非常保守的,只有100%确定当前是单线程),如果当前逻辑不需要加锁,编译器就会自动去除synchronized。

  • 锁粗化:一个代码对细粒度的代码反复加锁解锁,就会将这个步骤优化为更粗粒度的加锁解锁。

  • 锁的粒度:加锁和解锁之间,包含的代码执行的逻辑/时间越多,则锁的粒度就越粗,反之越细。

三、CAS

3.1 CAS概念

CAS: 全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。

  1. 比较 A 与 V 是否相等。(比较)
  2. 如果比较相等,将 B 写入 V。(交换)
  3. 返回操作是否成功。

伪代码表示如下:

boolean CAS(address, expectValue, swapValue) {if (&address == expectedValue) {&address = swapValue;return true;}return false;
}

3.2 CAS应用场景

3.2.1 实现原子类。

原子类:特指java.util.concurrent.atomic 包下的类,这些类中的操作都是原子的。

像我们前面Thread类详解,这篇文章介绍的非原子的操作带来的线程安全问题,如果使用原子类就不存在了。
例如执行下面这段代码:结果就一直是100000。


import java.util.concurrent.atomic.AtomicInteger;public class Demo {private static AtomicInteger count = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.getAndAdd(1);}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.getAndAdd(1);}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(count.get());}
}

原子类线程安全的原理:

假设两个线程同时调用 getAndIncrement :

  1. 两个线程都读取 value 的值到 oldValue 中。 (oldValue 是一个局部变量, 在栈上. 每个线程有自己的栈)
  2. 线程1 先执行 CAS 操作. 由于 oldValue 和 value 的值相同, 直接进行对 value 赋值。
  3. 线程2 再执行 CAS 操作, 第一次 CAS 的时候发现 oldValue 和 value 不相等, 不能进行赋值。因此需要进入循环。在循环里重新读取 value 的值赋给 oldValue。
  4. 线程2 接下来第二次执行 CAS, 此时 oldValue 和 value 相同, 于是直接执行赋值操作。
  5. 线程1 和 线程2 返回各自的 oldValue 的值即可。

3.2.2 实现自旋锁

基于 CAS 实现更灵活的自旋锁, 获取到更多的控制权。

主要逻辑如下:

  • 通过 CAS 看当前锁是否被某个线程持有。
  • 如果这个锁已经被别的线程持有, 那么就自旋等待。
  • 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.

伪代码描述如下:

public class SpinLock {private Thread owner = null;public void lock(){while(!CAS(this.owner, null, Thread.currentThread())){}}public void unlock (){this.owner = null;}
}

3.3 CAS的ABA问题

3.3.1 ABA问题简介

假设存在两个线程 t1 和 t2. 有一个共享变量 num, 初始值为 A。
接下来, 线程 t1 想使用 CAS 把 num 值改成 Z, 那么就需要先读取 num 的值, 记录到 oldNum 变量中.。
使用 CAS 判定当前 num 的值是否为 A, 如果为 A, 就修改成 Z。
但是, 在 t1 执行这两个操作之间, t2 线程可能把 num 的值从 A 改成了 B, 又从 B 改成了 A。
线程 t1 的 CAS 是期望 num 不变就修改. 但是 num 的值已经被 t2 给改了. 只不过又改成 A 了. 这
个时候 t1 究竟是否要更新 num 的值为 Z 。就不符合预期出现bug。

过程图:

bug例子:

假如你去取钱有1000,取500,不小心按了两次取款,我们只想扣一次500,第一次按相当于T2,第二次按相当于T1,先执行T2读取操作修改为500,这时又正好有人给你汇款了500,内存中与寄存器中又相等了,T1扣款的操作也会执行成功。

造成ABA问题的原因就是内存值既有加又有减,所以我们的解决ABA的方式如下。

3.3.2 解决方案

给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期。
CAS 操作在读取旧值的同时, 也要读取版本号。
真正修改的时候,:

  • 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1。
  • 如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了)。

四、JUC组件

JUC组件就是指 java.util.concurrent 这个包下面的类。

4.1 Callable接口

Callable 接口就和java.lang包下的Runnable接口的定位差不多,只不过给了我们一个返回值,在Runnable接口要重写run方法,而在Callable接口中要重写call方法。

在使用Callable接口传入线程时需要借助JUC下的另一个类FutureTask。

使用例子一般如下:

public class Demo {public static void main(String[] args) {//创建实现了Callable接口的方法Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {return null;}};//将实现Callable接口的对象作为参数传入FutureTask,泛型参数要一致FutureTask<Integer> futureTask = new FutureTask<>(callable);//将FutureTask对象作为参数传入Thread thread = new Thread(futureTask);}
}

4.2 ReentrantLock类

ReentrantLock类可重入互斥锁,和synchronized的定位是差不多的。

ReentrantLock 的用法:

  • public void lock(): 加锁, 如果获取不到锁就死等。
  • public boolean trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁。
  • public void unlock(): 解锁。
    使用这个类加锁需要我们手动解锁,为避免解锁执行不到的情况,我们一般将unlock放在finally代码块中。
		ReentrantLock locker = new ReentrantLock();try {locker.lock();}finally {locker.unlock();}

ReentrantLock与synchronized的区别:

  • ReentrantLock是类,是Java代码实现的,synchronized是关键字由JVM使用c++代码实现的。
  • ReentrantLock是需要lock()方法加锁,unlock()方法解锁。synchronized是通过进出代码块加锁解锁。
  • ReentrantLock还提供了tryLock()方法可以设置超时时间,不会一直阻塞,加锁成功返回true,调用者可以根据返回值自己操作。
  • ReentrantLock还提供了公平锁的实现,默认是非公平锁,但是构造方法传参true实现公平锁。
  • ReentrantLock搭配的通知机制是Condition类,可以更精确控制唤醒某个指定的线程,相比wait / notify功能更强大。

4.3 Semaphore类

信号量,:用来表示 “可用资源的个数”。 本质上就是一个计数器,能够协调多个线程之间的资源调配。
最主要的就是申请资源(P操作,调用acquire方法)释放资源(V操作,调用release方法)

  • 可以把信号量想象成是停车场的展示牌: 当前有车位 100 个。表示有 100 个可用资源。
  • 当有车开进去的时候, 就相当于申请一个可用资源, 可用车位就 -1 (这个称为信号量的 P 操作)
  • 当有车开出来的时候, 就相当于释放一个可用资源, 可用车位就 +1 (这个称为信号量的 V 操作)
  • 如果计数器的值已经为 0 了, 还尝试申请资源, 就会阻塞等待, 直到有其他线程释放资源.

4.4 CountDownLatch类

CountDownLatch类的作用:同时等待 N 个任务执行结束。(就像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。)

使用多线程的时候,经常会把一个大任务拆分为多个子任务,使用CountDownLatch衡量子任务完成,让整个任务完成。

  • 构造 CountDownLatch 实例, 参数传入任务个数,初始化 10 表示有 10 个任务需要完成。
  • 每个任务执行完毕, 都调用 countDown()方法 . 在 CountDownLatch 内部的计数器同时自减.
  • 主线程中使用 await()方法; 阻塞等待所有任务执行完毕后wait结束。 相当于计数器为 0 了。

相关文章:

【JavaEE】【多线程】进阶知识

目录 一、常见的锁策略1.1 悲观锁 vs 乐观锁1.2 重量级锁 vs 轻量级锁1.3 挂起等待锁 vs 自旋锁1.4 普通互斥锁 vs 读写锁1.5 可重入锁 vs 不可重入锁1.6 不公平锁 vs 公平锁 二、synchronized特性2.1 synchronized的锁策略2.2 synchronized加锁过程2.3 其它优化措施 三、CAS3.…...

LeetCode100之三数之和(15)--Java

1.问题描述 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意 答案中不可以包含重复的三元组 示例1 输入&…...

并发编程三大特性--可见性和有序性

可见性&#xff1a; 什么是可见性&#xff1a; 可见性是指在数据在收到一个线程的修改时&#xff0c;其他的线程也可以得知并获取修改后的值的属性。这是并发编程的三大特性之一。 为了提高cpu的利用率&#xff0c;cpu在获取数据时&#xff0c;不是直接在主内存读取数据&…...

Android 使用ninja加速编译的方法

ninja的简介 随着Android版本的更迭&#xff0c;makefile体系逐渐增多&#xff0c;导致make单编模块的时间越来越长&#xff0c;每次都需要半个小时甚至更长时间&#xff0c;其原因为每次make都会重新加载所有mk文件&#xff0c;再生成ninja编译&#xff0c;此完整过程十分耗时…...

《Java 实现选择排序:原理剖析与代码详解》

目录 一、引言 二、选择排序原理 三、代码分析 1. 代码整体结构 2. main方法 3. sort方法&#xff08;选择排序核心逻辑&#xff09; 四、测试结果 一、引言 排序算法在计算机科学领域中是非常重要的一部分&#xff0c;它能够帮助我们将无序的数据按照特定的顺序进行排列…...

数据结构之双链表——考研笔记

文章目录 一.单链表VS双链表二.创建双链表&#xff08;带头结点&#xff09;三.双链表的插入四.双链表删除五.销毁双链表六.双链表遍历七. 循环链表八.静态链表1.用代码定义一个静态链表 一.单链表VS双链表 单链表中只包含指向它后继结点的指针&#xff0c;所以给定一个结点p找…...

Django视图写法

1.View&#xff1a;Django默认的视图基类,Django的HttpRequeset对象 2.APIView&#xff1a;REST-framework提供的所有视图的基类,继承自Django的View REST framework的Request对象 Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。 serializer Book…...

单臂路由实现不同VLAN之间设备通信

转载请注明出处 本实验为单臂路由配置&#xff0c;目的为让不同VLAN之间的设备能够互相通信。 1.首先&#xff0c;按照要求配置两个pc的ip地址&#xff0c;以pc0为例子&#xff1a; 2在交换机创建vlan10和vlan20 3.划分vlan&#xff0c;pc0为vlan10的设备&#xff0c;pc1为vla…...

Linux·进程控制(system V)

1. 共享内存 system V共享内存是最快的IPC形式&#xff0c;之前的管道是基于Linux内核开发的通讯方案&#xff0c;其读写接口都是现成的&#xff0c;因此内核设计者为了完成进程间通讯任务并不需要新增太多代码。而共享内存属于system V标准&#xff0c;是操作系统单独…...

华为云Stack名词解释

1、MRS MapReduce服务&#xff08;MRS&#xff09;是一种基于云计算平台的即开即用、稳定可靠、弹性伸缩、便捷管理的数据处理分析服务。 2、VBS 云硬盘备份服务&#xff08;VBS&#xff0c;Volume Backup Service&#xff09;可为云硬盘&#xff08;EVS&#xff0c;Elastic…...

YoloV9改进策略:上采样改进|CARAFE,轻量级上采样|即插即用|附改进方法+代码

论文介绍 CARAFE模块概述&#xff1a;本文介绍了一种名为CARAFE&#xff08;Content-Aware ReAssembly of FEatures&#xff09;的模块&#xff0c;它是一种用于特征上采样的新方法。应用场景&#xff1a;CARAFE模块旨在改进图像处理和计算机视觉任务中的上采样过程&#xff0…...

【C++】多态的语法与底层原理

1.多态的概念 1.1 概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会 产生出不同的状态。 举个例子&#xff1a;在现实当中&#xff0c;我们去火车站买票&#xff0c;一般都分三种情况&…...

RTP和RTCP的详细介绍及其C代码示例

RTP和RTCP的详细介绍及其C代码示例 RTP和RTCP简介RTP协议详解RTCP协议详解RTP和RTCP之间的关系C代码示例RTP和RTCP简介 RTP(Real-time Transport Protocol,实时传输协议)和RTCP(Real-time Transport Control Protocol,实时传输控制协议)是流媒体传输中常用的两个协议。R…...

深入浅出了解AI教育发展与落地应用情况

2023年,是生成式AI能力涌现的一年,通用大模型是其中的主旋律。经过一年的发展,通用大模型格局已初步形成,生成式AI也从能力展示走向应用落地。进入2024年,对生成式AI的讨论和实践也都转向如何赋能产业。相比于通用大模型,进入产业内的大模型需要的是对行业的Know-How,以…...

Hive数据库操作语法

数据类型 内部表和外部表 内部表 &#xff08;CREATE TABLE table_name ......&#xff09;未被external关键字修饰的即是内部表&#xff0c; 即普通表。 内部表又称管理表,内部表数据存储的位置由hive.metastore.warehouse.dir参数决定&#xff08;默认&#xff1a;/user/h…...

容器架构-Docker的成长之路

目录 1. 什么是容器 2. 容器 vs 虚拟机 3. Docker极速上手指南 环境准备 3.1 配置docker源 3.2 下载镜像加速的配置 3.3 下载自动补全工具 4. Docker C/S架构 5. Docker的镜像管理 5.1 下载nginx:alpine镜像并查看 5.2 sl大法 5.3 删除镜像 5.4 镜像清理用的命令 5…...

关于我、重生到500年前凭借C语言改变世界科技vlog.14——常见C语言算法

文章目录 1.冒泡排序2.二分查找3.转移表希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&#xff01; 根据当前所学C语言知识&#xff0c;对前面知识进行及时的总结巩固&#xff0c;出了这么一篇 vlog 介绍当前所学知识能遇到的常见算法&#xff0c;这些算法是…...

简记Vue3(三)—— ref、props、生命周期、hooks

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…...

ARM cpu算力KDMIPS测试

一、引言 KDMIPS(KiloDhrystone Million Instructions Per Second)是一种衡量处理器性能的指标,它表示处理器每秒钟可以执行多少百万条Dhrystone指令。 二、测试说明 1、将cpu模式调整为perfermance 2、将cpu的频率和gpu的频率调大最大 3、将ddr和各core的电压和频率调大最…...

自杀一句话木马(访问后自动删除)

在做安全测试时&#xff0c;例如文件上传时就要上传可以解析的脚本文件解析证明存在漏洞&#xff0c;这个时候就需要(访问后自动删除文件的一句话木马) PHP <?php echo md5(1);unlink(__FILE__); ?> 访问后自动删除...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

docker 部署发现spring.profiles.active 问题

报错&#xff1a; org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

Java数值运算常见陷阱与规避方法

整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)

目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 ​编辑​编辑 UDP的特征 socke函数 bind函数 recvfrom函数&#xff08;接收函数&#xff09; sendto函数&#xff08;发送函数&#xff09; 五、网络编程之 UDP 用…...

【深度学习新浪潮】什么是credit assignment problem?

Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...