Java 面试题:从源码理解 ThreadLocal 如何解决内存泄漏 ConcurrentHashMap 如何保证并发安全 --xunznux
文章目录
- ThreadLocal
- ThreadLocal 的基本原理
- ThreadLocal 的实现细节
- 内存泄漏
- 源码
- 使用场景
- ConcurrentHashMap 怎么实现线程安全的
- CAS初始化源码
- 添加元素putVal方法
ThreadLocal
ThreadLocal 是 Java 中的一种用于在多线程环境下存储线程局部变量的机制,它可以为每个线程提供独立的变量副本,从而避免多个线程之间的竞争条件。ThreadLocal 在实际应用中,特别是在需要在线程间共享资源的场景下,发挥着重要作用。
ThreadLocal 的基本原理
ThreadLocal 的核心概念是为每个线程维护一个独立的变量副本。当一个线程通过 ThreadLocal 访问某个变量时,实际上访问的是属于该线程的独立副本。ThreadLocal 通过以下几个关键点实现了这一点:
- 每个线程持有自己的 ThreadLocalMap:
每个线程内部都有一个 ThreadLocalMap 对象,这个对象存储了 ThreadLocal 变量及其对应的值。ThreadLocalMap 是 Thread 类中的一个成员变量,因此它与线程的生命周期绑定。 - ThreadLocalMap** 的结构**:
ThreadLocalMap 是一个定制的哈希表,它的键是 ThreadLocal 对象,而值是对应的线程局部变量的值。每个线程持有的 ThreadLocalMap 可以存储多个 ThreadLocal 变量。 - 变量的存取过程:
- 当线程调用 ThreadLocal 的 get() 方法时,ThreadLocal 会获取当前线程持有的 ThreadLocalMap,然后通过自身(作为键)从 ThreadLocalMap 中获取变量的值。
- 当线程调用 ThreadLocal 的 set() 方法时,ThreadLocal 会将变量的值存储到当前线程持有的 ThreadLocalMap 中。
ThreadLocal 的实现细节
- ThreadLocal类:
ThreadLocal 本身只是提供了一套访问接口,它内部依赖于 ThreadLocalMap 来存储和获取线程局部变量。 - ThreadLocalMap的实现:
- ThreadLocalMap 是一个内部类,它的结构类似于一个简化的哈希表。ThreadLocalMap 使用了一个简单的开放地址法来处理哈希冲突。
- 每个键值对的键是一个 ThreadLocal 的弱引用(WeakReference),这有助于避免内存泄漏:当 ThreadLocal 对象被回收后,键会变成 null,相应的值也会被清理。
- ThreadLocalMap的垃圾回收:
由于 ThreadLocalMap 使用了弱引用,ThreadLocal 对象不会阻止其被垃圾回收机制回收。当 ThreadLocal 对象被回收后,ThreadLocalMap 中对应的键会变成 null,但是值仍然会存在。这种情况下,如果不及时清理,可能会导致内存泄漏。 - remove()方法:
ThreadLocal 提供了一个 remove() 方法,可以显式地将当前线程持有的 ThreadLocal 变量移除。这有助于防止内存泄漏,特别是在使用线程池的场景下,线程会被重复利用,如果不清理,可能会导致数据污染或内存泄漏。
内存泄漏
实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。
所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。
ThreadLocal其实是与线程绑定的一个变量,如此就会出现一个问题:如果没有将ThreadLocal内的变量删除(remove)或替换,它的生命周期将会与线程共存。通常线程池中对线程管理都是采用线程复用的方法,在线程池中线程很难结束甚至于永远不会结束,这将意味着线程持续的时间将不可预测,甚至与JVM的生命周期一致。
如何避免内存泄漏:
- 每次使用完threadlocal调用remove方法清除
- 尽可能把threadlocal变量定义为static final,这样可以避免频繁创建实例。
- 内部优化
- 调用set()方法,会采用采样清理,全量清理,扩容时还能继续检查
- 调用get()方法,如果没有命中,向后环形查找时进行清理。
- 调用remove()方法,清理当前entry,还会向后清理
最好是使用完之后手动调用remove方法,这个方法底层会调用map的remove将 Entry 移除。
因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。弱引用只是保证了 ThreadLocal 会被 GC 自动回收。
最佳做法:每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
源码
static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e);}
}
该Java静态内部类Entry继承自WeakReference<ThreadLocal<?>>,主要用于存储ThreadLocal与其关联对象的弱引用及其对应的值。具体说明如下:
value:存储与ThreadLocal关联的具体值。
构造方法接收一个ThreadLocal对象和一个值,创建一个弱引用来持有ThreadLocal对象,并保存关联值。
使用场景
ThreadLocal 常用于以下场景:
- 数据库连接管理:每个线程持有一个独立的数据库连接,避免多个线程同时使用同一个连接。
- 用户上下文信息:在 Web 应用中,每个线程处理一个用户请求,可以通过 ThreadLocal 存储和访问该用户的上下文信息。
- 线程安全的对象实例:通过 ThreadLocal 为每个线程创建独立的对象实例,避免线程间的竞争条件。
注意事项 - 内存泄漏:如果 ThreadLocal 变量不及时清理,可能会导致内存泄漏,尤其是在使用线程池时要特别注意调用 remove() 方法。
- 适用场景:ThreadLocal 适合用于线程独立的数据存储,不适合跨线程的数据共享。
总结来说,ThreadLocal 是一种通过在每个线程中创建独立变量副本的方式,来实现线程隔离的工具,它的底层依赖于每个线程持有的 ThreadLocalMap 来存储这些变量副本,从而确保线程间的数据独立性和安全性。
例子:
ThreadLocalMap 中的 Key 和 Value
- Key:
- 在 ThreadLocalMap 中,Key 是 ThreadLocal 实例本身。ThreadLocal 对象作为键,指向当前线程所持有的变量。
- Value:
- Value 是由 ThreadLocal 对象所关联的值。在本例中,Value 是 Integer,存储的是每个线程独立的计数器值。
每个线程都有一个 ThreadLocalMap,这个 ThreadLocalMap 使用 ThreadLocal 实例作为键来存储和获取对应的线程局部变量值,从而实现数据隔离。
public class MultipleThreadLocalExample {// 定义两个不同的 ThreadLocal 变量private static ThreadLocal<Integer> threadLocalCounter1 = ThreadLocal.withInitial(() -> 0);private static ThreadLocal<String> threadLocalCounter2 = ThreadLocal.withInitial(() -> "Initial Value");public static void main(String[] args) {// 启动一个线程,独立操作两个 ThreadLocal 变量Thread thread = new Thread(() -> {// 操作第一个 ThreadLocal 变量threadLocalCounter1.set(threadLocalCounter1.get() + 10);System.out.println(Thread.currentThread().getName() + " - Counter1: " + threadLocalCounter1.get());// 操作第二个 ThreadLocal 变量threadLocalCounter2.set(threadLocalCounter2.get() + " Updated");System.out.println(Thread.currentThread().getName() + " - Counter2: " + threadLocalCounter2.get());});thread.start();}
}
多个 ThreadLocal 变量在同一线程中的工作机制
- ThreadLocalMap 的键区分:
- 在同一个线程中,不同的 ThreadLocal 变量对应着不同的键。这些键就是 ThreadLocal 对象本身。因此,即使在同一个线程中,每个 ThreadLocal 实例都能独立存储和访问自己的值,不会与其他 ThreadLocal 变量发生冲突。
- ThreadLocalMap 的存储结构:
- ThreadLocalMap 通过一个哈希表来存储键值对。键是 ThreadLocal 对象,值是线程局部变量的实际数据。因此,同一线程中的多个 ThreadLocal 变量不会相互覆盖或混淆。
ConcurrentHashMap 怎么实现线程安全的
采用了CAS算法(compareAndSwapObject)和部分代码使用synchronized锁保证线程安全。
对应的非并发容器:HashMap
目标:代替Hashtable、synchronizedMap,支持复合操作。
原理:JDK6中采用一种更加细粒度的加锁机制 Segment “分段锁”,JDK8中采用 volatile + CAS 或者 synchronized 。
**添加元素时首先会判断容器是否为空:
- 如果为空则使用 volatile 加 CAS 来初始化
- 如果容器不为空,则根据存储的元素计算该位置是否为空。
- 如果根据存储的元素计算结果为空,则利用 CAS 设置该节点;
- 如果根据存储的元素计算结果不为空,则使用 synchronized ,然后,遍历桶中的数据,并替换或新增节点到桶中,最后再判断是否需要转为红黑树,这样就能保证并发访问时的线程安全了**。
如果把上面的执行用一句话归纳的话,就相当于是ConcurrentHashMap通过对头结点加锁来保证线程安全的,锁的粒度相比 Segment 来说更小了,发生冲突和加锁的频率降低了,并发操作的性能就提高了。
而且 JDK 1.8 使用的是红黑树优化了之前的固定链表,那么当数据量比较大的时候,查询性能也得到了很大的提升,从之前的 O(n) 优化到了 O(logn) 的时间复杂度。
CAS初始化源码
这段 Java 代码定义了一个名为 sizeCtl 的私有变量,其类型为 int,并且被 transient 和 volatile 修饰符所修饰。这个变量在类中的作用如下:
Table 初始化和调整大小控制:
- 当 sizeCtl 的值为负数时,表示当前正在进行表的初始化或调整大小操作。
- -1 表示正在初始化表。
- 其他负数值(如 -2, -3 等)表示正在进行调整大小的操作,并且 -1 减去该值即为当前活跃的调整大小线程的数量。
初始表大小或默认值:
- 如果表(table)为空,并且 sizeCtl 的值为非负数,则该值表示创建表时应使用的初始大小。
- 如果 sizeCtl 的值为 0,则表示使用默认大小创建表。
调整大小的阈值: - 在初始化之后,sizeCtl 的值表示下次应该调整表大小时元素的数量阈值。
变量修饰符说明: - transient:表示这个变量不会被序列化。当对象被序列化成字节流时,sizeCtl 的值不会被保存。
- volatile:确保多线程环境下的可见性和有序性,即任何线程对 sizeCtl 的修改都会立即反映到其他线程中。
通过这种方式,sizeCtl 变量帮助实现了并发哈希表(如 ConcurrentHashMap)在初始化和动态调整大小过程中的控制逻辑。
private transient volatile int sizeCtl;
private final Node<K,V>[] initTable() {Node<K,V>[] tab; int sc;while ((tab = table) == null || tab.length == 0) {if ((sc = sizeCtl) < 0)Thread.yield(); // lost initialization race; just spin提示调度器当前线程愿意放弃处理器使用权else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {try {if ((tab = table) == null || tab.length == 0) {int n = (sc > 0) ? sc : DEFAULT_CAPACITY;@SuppressWarnings("unchecked")Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];table = tab = nt;sc = n - (n >>> 2);}} finally {sizeCtl = sc;}break;}}return tab;
}
该函数初始化哈希表,主要功能如下:
- 检查当前表格是否为空或长度为零。
- 使用CAS操作安全地初始化表格数组。
- 如果成功,则根据sizeCtl记录的大小创建新数组,并更新sizeCtl值。
添加元素putVal方法
/*** 核心方法,用于处理put和putIfAbsent操作* 该方法实现了哈希表的插入逻辑,包括处理哈希冲突和数据结构转换(链表转红黑树)* * @param key 键,不能为null* @param value 值,不能为null* @param onlyIfAbsent 如果为true,则仅在键不存在时进行插入* @return 插入前该键对应的旧值,如果没找到则返回null*/final V putVal(K key, V value, boolean onlyIfAbsent) {// 检查键值对是否为null,为null则抛出异常if (key == null || value == null) throw new NullPointerException();// 扩散哈希码,以减少哈希冲突int hash = spread(key.hashCode());int binCount = 0; // 用于记录链表或红黑树中的元素数量// 循环尝试在哈希表中插入值for (Node<K,V>[] tab = table;;) {Node<K,V> first; int n, i, firstHash;// 表为空时初始化哈希表(volatile和CAS)if (tab == null || (n = tab.length) == 0)tab = initTable();// 位置i处的节点为空,直接插入新节点else if ((first = tabAt(tab, i = (n - 1) & hash)) == null) {// 存储的元素在该node数组的下标位置结果为空,则利用 CAS 设置该节点if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))break; // 成功插入,跳出循环}// 位置i处的节点处于迁移中,帮助完成迁移else if ((firstHash = first.hash) == MOVED)tab = helpTransfer(tab, first);// 位置i处的节点正常,进行插入或更新操作else {V oldVal = null;// 同步锁,确保线程安全。存储的元素计算结果不为空,则使用 synchronizedsynchronized (first) {// 再次检查节点,防止并发修改if (tabAt(tab, i) == first) {// 链表形式,遍历链表找到键或插入新节点if (firstHash >= 0) {binCount = 1;for (Node<K,V> e = first;; ++binCount) {K ek;// 找到匹配的键,更新值并跳出循环if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}Node<K,V> pred = e;// 链表末尾插入新节点if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key,value, null);break;}}}// 红黑树形式,调用红黑树的插入或更新方法else if (first instanceof TreeBin) {Node<K,V> p;binCount = 2;if ((p = ((TreeBin<K,V>)first).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}// 链表节点数超过阈值,转换为红黑树if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);// 找到旧值,返回旧值if (oldVal != null)return oldVal;break;}}}// 更新哈希表大小和修改次数addCount(1L, binCount);return null; // 未找到旧值,返回null}
相关文章:

Java 面试题:从源码理解 ThreadLocal 如何解决内存泄漏 ConcurrentHashMap 如何保证并发安全 --xunznux
文章目录 ThreadLocalThreadLocal 的基本原理ThreadLocal 的实现细节内存泄漏源码使用场景 ConcurrentHashMap 怎么实现线程安全的CAS初始化源码添加元素putVal方法 ThreadLocal ThreadLocal 是 Java 中的一种用于在多线程环境下存储线程局部变量的机制,它可以为每…...

使用人力劳务灵工安全高效的发薪工具
实现企业、劳务、蓝领工人三方的需求撮合、劳务交付、日结考勤、薪费结算一体化闭环,全面为人力企业降低用工成本、提高用工效率。 发薪难 日结/周结/临时工人员难管理,考勤难统计,发薪耗时间 发薪慢 人工核算时间长,微信转账发薪容易限额…...

使用W外链创建微信短链接的方法
创建短链是将长链接转换为更短、更易于分享和记忆的链接的过程。W外链是一个提供短链接生成服务的平台,它支持多种功能,包括但不限于: 短链制作:用户可以将长链接缩短为易于分享的短链接,还支持自定义短链后缀。防红防…...

【人工智能学习笔记】4_4 深度学习基础之生成对抗网络
生成对抗网络(Generative Adversarial Network, GAN) 一种深度学习模型,通过判别模型(Discriminative Model)和生成模型(Generative Model)的相互博弈学习,生成接近真实数据的数据分…...

基于MinerU的PDF解析API
基于MinerU的PDF解析API - MinerU的GPU镜像构建 - 基于FastAPI的PDF解析接口支持一键启动,已经打包到镜像中,自带模型权重,支持GPU推理加速,GPU速度相比CPU每页解析要快几十倍不等 主要功能 删除页眉、页脚、脚注、页码等元素&…...

猫头虎分享:看完百度内部讲话,整理出李彦宏关于大模型的10个判断
🦁 猫头虎分享:看完百度内部讲话,整理出李彦宏关于大模型的10个判断 📢 大家好!我是猫头虎技术团队的首席写作官。今天为大家带来一篇重量级内容:从百度内部讲话中,整理了李彦宏对大模型的10大…...

vue3透传、注入
属性透传 传递给子组件时,没有被子组件消费的属性或事件,常见的如id、class 注意1 1.class、style是合并的,style中如果出现重复的样式,以透传属性为准2.id属性是以透传属性为准,其他情况透传属性名相同,…...

数模原理精解【9】
文章目录 混合高斯分布概述定义性质参数估计计算Julia实现 详述定义原理 核心参数1. 均值(Means)2. 协方差矩阵(Covariance Matrices)3. 权重(Weights)4. 聚类个数(高斯模型个数,K&a…...

Java中的linkedList类及与ArrayList的异同
继承实现关系 public class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deque<E>, Cloneable, java.io.Serializable 由于涉及的类过多,画起来过于繁琐,这里只展示最外层的继承实现关系 可以看到它是…...

【精选】文件摆渡系统:跨网文件传输的安全与效率之选
文件摆渡系统可以解决哪些问题? 文件摆渡系统(File Shuttle System)主要是应用于不同网络、网段、区域之间的文件数据传输流转场景, 用于解决以下几类问题: 文件传输问题: 大文件传输:系统可…...

tkinter 电子时钟 实现时间日期 可实现透明 无标题栏
下面是一个使用tkinter库实现的简单电子时钟的例子,可以显示当前的日期和时间,并且可以设置窗口为透明且无标题栏。 import tkinter as tk import timedef update_time():current_time time.strftime("%Y-%m-%d %H:%M:%S")label.config(text…...

【hot100-java】【除自身以外数组的乘积】
R8-普通数组篇 印象题,计算前缀,计算后缀,计算乘积。 class Solution {public int[] productExceptSelf(int[] nums) {int n nums.length;int[] prenew int[n];pre[0]1;for (int i1;i<n;i){pre[i]pre[i-1]*nums[i-1];}int[] sufnew int[…...

【Python机器学习】循环神经网络(RNN)——审察模型内部情况
Keras附带了一些工具,比如model.summary(),用于审察模型内部情况。随着模型变得越来越复杂,我们需要经常使用model.summary(),否则在调整超参数时跟踪模型内部的内容的变化情况会变得非常费力。如果我们将模型的摘要以及验证的测试…...

智能语音交互:人工智能如何改变我们的沟通方式?
在科技飞速发展的今天,人工智能(AI)已经渗透到我们生活的方方面面。其中,智能语音交互作为AI技术的一个重要分支,正以前所未有的速度改变着我们的沟通方式。从智能家居的控制到办公自动化的应用,再到日常交…...

vue3中动态引入本地图片的两种方法
方法一 <img width"10" height"10":src"/src/assets/nncs2/jiantou${index 1}.png" alt"" /> 推荐 简单好用 方法二 const getImg index > {const modules import.meta.glob(/assets/nncs2/**/*.{png,svg,jpg,jpeg}, { …...

Linux网络——socket编程与UDP实现服务器与客户机通信
文章目录 端口号TCP/UDP网络字节序socket的常见APIUDP实现服务器与客户机通信服务器客户机运行效果如下 端口号 我们说即便是计算机网络,他们之间的通信也仍然是进程间通信 那么要如何在这么多计算机中,找到你想要的那个进程呢 在网络中标识的唯一的计…...

大型语言模型中推理链的演绎验证
大语言模型(LLMs)在执行各种推理任务时,由于引入了链式推理(Chain-of-Thought,CoT)提示,显著受益。尽管CoT使模型产生更全面的推理过程,但其对中间推理步骤的强调可能会无意中引入幻…...

openharmony 应用支持常驻和自启动
本文环境: devEco studio 版本 4.0.0.600 SDK版本:3.2.12.5 full SDK 应用模型:Stage 功能简介: OpenHarmony支持包含ServiceExtensionAbility类型模块的应用配置常驻和自启动。 关于ServiceExtensionAbility其他的介绍可以参考官网:ServiceExtensionAbility(仅对…...

Winform中引入WPF控件后键盘输入无响应
引言 Winform中如何引入WPF控件的教程很多,对于我们直接通过ElementHost引入的直接显示控件,它是可以响应键盘输入消息的,但对于在WFP中弹出的窗体来说,此时是无法响应我们的键盘输入的。我们需要给它使能键盘输入。 1、使能键盘…...

多线程——死锁
死锁 在Java中使用多线程,就会有可能导致死锁问题。死锁会让程序一直卡住,程序不再往下执行。 我们只能通过中止并重启的方式来让程序重新执行。 这是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生! 死锁的原因…...

链路追踪可视化利器之火焰图
随着现代化技术的发展,为了能够保证 IT 系统的稳定性、高扩容性,企业往往采用分布式的方式来构建 IT 系统。但也正因为如此,IT 系统中涉及到的服务和组件可能被分布在不同的服务器、数据中心甚至不同的地理位置,这导致应用发生故障…...

C语言 ——— 条件编译指令实际用途
目录 前言 头文件被包含的方式 嵌套文件包含 使用条件编译指令规避头文件多次包含 还有一个编译指令,同样能做到以上功能 前言 条件编译指令多用于对头文件的定义和判断以及删除 头文件被包含的方式 本地文件包含(也就是自己创建的头文件ÿ…...

备战软考Day01-计算机系统
1.数值及其转化 1.数值转化(十进制) 2.十进制推广 3.进制转化 4.数据表示 1.原码 2.反码 3.补码 4.移码 5.定点数 就是小数点的位置固定不变的数。小数点的位置通常有两种约定方式:定点整数(纯整数,小数点在最低有效数值位之后…...

从C语言过渡到C++
📔个人主页📚:秋邱-CSDN博客☀️专属专栏✨:C 🏅往期回顾🏆:单链表实现:从理论到代码-CSDN博客🌟其他专栏🌟:C语言_秋邱的博客-CSDN博客 目录 …...

Docker 的安装和使用
参考资料: 通俗易懂了解什么是docker?Docker 教程 | 菜鸟教程Ubuntu 22.04 安装 DockerDocker 超详细基础教程WSL2 支持 systemctl 命令systemd 和 systemctl 是什么?使用正确的命令重启 WSL 子系统Ubuntu 修改源镜像方法Docker 中出现 ‘/etc/resolv.…...

鸿蒙轻内核A核源码分析系列七 进程管理 (2)
往期知识点记录: 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总 轻内核A核源码分析系列一 数据结构-双向循环链表 轻内核A核源码分析系列二 数据结构-位图操作 轻内核A核源码分析系列三 物理内存(1࿰…...

关于TypeScript使用讲解
TypeScript讲解 安装环境 1.安装node js 配置环境变量 2.在终端中 运行 npm i -g typescript typescript: 用于编译ts代码 提供了 tsc命令 实现了将 TS>>>> JS转换 验证: tsc -v 编译并运行 TS代码 1.创建ts文件(TS文件为后缀名的文件࿰…...

C语言 | Leetcode C语言题解之第393题UTF-8编码验证
题目: 题解: static const int MASK1 1 << 7; static const int MASK2 (1 << 7) (1 << 6);bool isValid(int num) {return (num & MASK2) MASK1; }int getBytes(int num) {if ((num & MASK1) 0) {return 1;}int n 0;in…...

Netty权威指南:Netty总结-编解码与序列化
第四章 TCP粘包/拆包问题 4.1 TCP 粘包/拆包 TCP是流协议,也就是没有界限的的一串数据,底层并不知道上层业务数据的具体含义,也就是说一个完整的包可能会被拆分成多个包进行发送,也可能把几个小包封装成一个大的数据包发送。这就…...

FIDAVL:基于视觉语言模型的假图像检测与归因 !
FIDAVL:基于视觉语言模型的假图像检测与归因 ! 这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】 作者提出了FIDAVL:使用视觉语言模型进行虚假图像检测。FIDAVL是一…...