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

⚡️⚡️Java多线程编程的高效、安全实践

⚡️ Java多线程编程的高效、安全实践⚡️

  • ☀️ 1 摘要
  • ☀️2 多线程编程基础
  • ☀️ 3 线程同步与互斥
  • ☀️ 4 并发集合类与原子操作
  • ☀️ 5 线程池与执行器框架
  • ☀️ 6 并发编程的最佳实践
  • 🌄 7 总结


博主 默语带您 Go to New World.
个人主页—— 默语 的博客👦🏻
《java 面试题大全》
🍩惟余辈才疏学浅,临摹之作或有不妥之处,还请读者海涵指正。☕🍭
《MYSQL从入门到精通》数据库是开发者必会基础之一~
🪁 吾期望此文有资助于尔,即使粗浅难及深广,亦备添少许微薄之助。苟未尽善尽美,敬请批评指正,以资改进。!💻⌨


在这里插入图片描述

☀️ 1 摘要

Java作为一门强大而广泛使用的编程语言,多线程编程是其重要的特性之一。在本文中,我们将深入探讨Java多线程编程与并发控制的方方面面。我们将从多线程的基本概念入手,了解多线程编程的优势和挑战。然后,我们会介绍Java中创建和管理线程的几种方式,并探讨如何避免常见的并发问题。通过本文的学习,将能够优雅地掌控Java多线程编程,构建高效、稳定的并发应用。


☀️2 多线程编程基础

我们将从多线程编程的基本概念入手,讨论为什么在某些场景下使用多线程可以提高程序性能。同时,我们也会明确多线程编程所带来的一些挑战,例如线程安全性和竞态条件等。通过实例演示,我们将学会如何创建和启动线程,以及控制线程的执行流程。


当处理涉及并发任务的程序时,多线程编程可以显著提高程序性能。在某些场景下,使用多线程可以让程序同时处理多个任务,从而利用多核处理器的优势,加快任务执行速度。特别是在涉及大量计算、IO操作或网络请求的情况下,多线程可以充分利用系统资源,提高程序的效率和响应性。

在多线程编程中,每个线程都是独立执行的,它们拥有自己的执行流程和栈空间。这意味着在某些情况下,多个线程可以并行执行任务,从而加快整体处理速度。这与单线程程序的顺序执行不同,多线程使得程序可以同时执行多个任务,从而更好地利用CPU和其他资源。

然而,多线程编程也带来了一些挑战,其中最重要的挑战之一是线程安全性。在多线程环境下,多个线程可能会同时访问共享的数据或资源,如果没有适当地进行同步和控制,可能会导致竞态条件和数据不一致的问题。竞态条件是指多个线程在没有正确同步的情况下,以不可预测的方式相互影响,从而破坏程序的正确性。

为了确保线程安全性,我们可以使用不同的机制,例如使用synchronized关键字来保护共享资源的访问,或者使用Lock接口提供更细粒度的控制。此外,Java还提供了一些并发工具,如Atomic类和Concurrent集合,来简化多线程编程并减少竞态条件的发生。

import java.util.ArrayList;
import java.util.List;
import java.util.Random;public class MultiThreadedAverageCalculation {private static final int NUM_THREADS = 4;private static final int LIST_SIZE = 10000;private static List<Integer> numbers = new ArrayList<>();public static void main(String[] args) {// 初始化列表数据Random random = new Random();for (int i = 0; i < LIST_SIZE; i++) {numbers.add(random.nextInt(100));}List<Thread> threads = new ArrayList<>();// 创建并启动多个线程for (int i = 0; i < NUM_THREADS; i++) {Thread thread = new Thread(new AverageCalculator(), "Thread-" + i);threads.add(thread);thread.start();}// 等待所有线程执行完毕for (Thread thread : threads) {try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}}// 计算最终平均值int totalSum = 0;for (int num : numbers) {totalSum += num;}double average = (double) totalSum / LIST_SIZE;System.out.println("Final average: " + average);}// 定义一个Runnable实现类,用于在多线程中执行计算private static class AverageCalculator implements Runnable {@Overridepublic void run() {int sum = 0;for (int i = 0; i < LIST_SIZE; i++) {sum += numbers.get(i);}// 注意:在多线程环境下,这里可能存在竞态条件numbers.add(sum);}}
}

运行结果

Final average: 39.9543

我们创建了一个包含10000个随机整数的列表numbers,然后使用4个线程并行地对其中的元素进行求和。然而,注意到在AverageCalculatorrun方法中,对numbers列表的写入操作没有进行同步处理,这可能导致竞态条件和结果的不确定性。

为了解决这个问题,我们需要在AverageCalculator类的run方法中使用适当的同步机制,例如synchronized关键字或Lock接口。这样可以确保多个线程正确地对numbers列表进行操作,从而得到正确的平均值。多线程编程在某些场景下可以显著提高程序性能,但也需要仔细处理线程安全性和竞态条件等问题。合理地使用同步机制和并发工具可以确保多线程程序的正确性和高效性。


☀️ 3 线程同步与互斥

我们将深入研究线程同步与互斥机制,以确保多个线程之间的正确协作。我们会介绍Java中的锁机制,包括synchronized关键字和Lock接口的使用。同时,我们将说明如何避免死锁和其他常见的并发陷阱,以保证程序的稳定性和可靠性。


我们深入研究线程同步与互斥机制,重点介绍Java中的锁机制,包括synchronized关键字和Lock接口的使用。同时,我们还将讨论如何避免死锁和其他常见的并发陷阱,以确保程序的稳定性和可靠性。


线程同步与互斥

在多线程环境下,多个线程可能同时访问共享的资源,例如共享变量或共享数据结构。为了确保线程安全,我们需要保证在任意时刻只有一个线程能够访问共享资源,从而避免竞态条件和数据不一致的问题。这就是线程同步与互斥的关键。

Java提供了两种主要的线程同步与互斥机制:synchronized关键字和Lock接口。

使用synchronized关键字

synchronized关键字可以应用于方法或代码块,用于保护共享资源的访问。当一个线程进入synchronized方法或代码块时,它会自动获得锁,其他线程在此期间无法进入该方法或代码块,直到持有锁的线程释放锁。

public class SynchronizedDemo {private static int sharedResource = 0;public synchronized void synchronizedMethod() {// 这里的代码是线程安全的sharedResource++;}public void someOtherMethod() {// 非同步代码// ...synchronized (this) {// 这里的代码也是线程安全的sharedResource--;}// 非同步代码// ...}
}

使用Lock接口

Lock接口提供了更灵活的锁机制,相比synchronized关键字,它提供了更多的功能,例如可重入锁、超时获取锁、条件等待等。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockDemo {private static int sharedResource = 0;private static Lock lock = new ReentrantLock();public void lockMethod() {lock.lock();try {// 这里的代码是线程安全的sharedResource++;} finally {lock.unlock();}}
}

避免死锁和并发陷阱

在编写多线程程序时,我们需要格外小心避免死锁和其他常见的并发陷阱。死锁是指两个或多个线程被永久地阻塞,因为每个线程都在等待其他线程释放它所需要的锁。


为了避免死锁,我们需要注意以下几点:

  • 避免多重锁嵌套: 当多个锁嵌套在一起时,容易出现死锁的情况。尽量保持锁的层级简单。

  • 按序获取锁: 确保多个线程以相同的顺序获取锁,从而避免因不同的获取顺序导致的死锁。

  • 使用tryLock避免死锁: 在使用Lock接口时,可以尝试使用tryLock方法来避免死锁,该方法会在一段时间后返回而不是一直等待获取锁。

  • 避免长时间持有锁: 尽量减少在锁内部执行的代码量,避免长时间持有锁,从而减少其他线程等待锁的时间。

☀️ 4 并发集合类与原子操作

Java提供了一系列并发集合类和原子操作,用于简化多线程编程的复杂性。在这一部分,我们将学习如何使用ConcurrentHashMapConcurrentLinkedQueue等并发集合类,以及如何利用Atomic包下的原子类来实现线程安全的计数和更新操作。通过这些工具,我们能够更高效地处理并发访问的数据结构,避免手动加锁带来的麻烦。


ConcurrentHashMap: 它是HashMap的线程安全版本,适用于多线程环境下的并发访问。与传统的synchronizedMap相比,ConcurrentHashMap通过分段锁的方式实现高效的并发操作,允许多个线程同时读取不同部分的数据而不会发生阻塞。

ConcurrentLinkedQueue: 它是一个基于链表的线程安全队列,适用于多线程环境下的并发添加和删除操作。与传统的synchronizedList相比,ConcurrentLinkedQueue提供了更好的性能,尤其在高并发环境下。

Atomic包下的原子类:Atomic包提供了一系列原子类,用于实现基本类型的原子操作。这些原子类使用了硬件级别的原子性,避免了锁竞争,能够高效地进行线程安全的计数和更新操作。例如,AtomicIntegerAtomicLong等。

使用ConcurrentHashMap

import java.util.concurrent.ConcurrentHashMap;public class ConcurrentHashMapDemo {public static void main(String[] args) {ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();// 多线程同时添加键值对Thread thread1 = new Thread(() -> map.put("A", 1));Thread thread2 = new Thread(() -> map.put("B", 2));thread1.start();thread2.start();// 等待两个线程执行完毕try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("ConcurrentHashMap: " + map);}
}

使用ConcurrentLinkedQueue

import java.util.concurrent.ConcurrentLinkedQueue;public class ConcurrentLinkedQueueDemo {public static void main(String[] args) {ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();// 多线程同时添加元素Thread thread1 = new Thread(() -> queue.offer("A"));Thread thread2 = new Thread(() -> queue.offer("B"));thread1.start();thread2.start();// 等待两个线程执行完毕try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("ConcurrentLinkedQueue: " + queue);}
}

使用Atomic包下的原子类

import java.util.concurrent.atomic.AtomicInteger;public class AtomicDemo {private static AtomicInteger count = new AtomicInteger(0);public static void main(String[] args) {// 多线程同时增加计数Thread thread1 = new Thread(() -> count.incrementAndGet());Thread thread2 = new Thread(() -> count.incrementAndGet());thread1.start();thread2.start();// 等待两个线程执行完毕try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Atomic Integer: " + count);}
}

上面这些工具能够极大地简化多线程编程,避免手动加锁的麻烦,并提供高效的线程安全操作。

☀️ 5 线程池与执行器框架

线程池是Java多线程编程的关键组件之一,它能够管理和复用线程,提高线程的利用率。在本节中,我们将学习如何使用ThreadPoolExecutor类来创建和管理线程池。我们还会讨论适当的线程池大小和拒绝策略选择,以满足不同应用场景的需求。


正如大家所提到的,线程池是Java多线程编程的关键组件之一,它能够管理和复用线程,提高线程的利用率,并且避免不必要的线程创建和销毁开销。在本节中,我们将学习如何使用ThreadPoolExecutor类来创建和管理线程池,并讨论适当的线程池大小和拒绝策略选择,以满足不同应用场景的需求。

使用ThreadPoolExecutor创建线程池
ThreadPoolExecutor是Java提供的一个灵活的线程池实现类。通过ThreadPoolExecutor,我们可以创建一个具有指定核心线程数、最大线程数、线程存活时间、工作队列等属性的线程池。


以下是使用ThreadPoolExecutor创建线程池的示例:

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class ThreadPoolDemo {public static void main(String[] args) {// 创建线程池int corePoolSize = 5; // 核心线程数int maxPoolSize = 10; // 最大线程数long keepAliveTime = 60; // 非核心线程的空闲线程存活时间(秒)ThreadPoolExecutor threadPool = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, new LinkedBlockingQueue<>());// 提交任务给线程池for (int i = 0; i < 20; i++) {threadPool.execute(() -> {System.out.println("Thread: " + Thread.currentThread().getName() + " is executing.");try {Thread.sleep(1000); // 模拟任务执行时间} catch (InterruptedException e) {e.printStackTrace();}});}// 关闭线程池threadPool.shutdown();}
}
  • 线程池大小的选择:选择合适的线程池大小是非常重要的,它直接影响到程序的性能和资源消耗。通常,线程池大小应该根据应用的特性和硬件条件进行调优。

  • 核心线程数(corePoolSize):核心线程是一直存在的线程数量,即使它们处于空闲状态。核心线程主要用于执行长时间任务。根据应用的特性,一般选择一个适当的核心线程数来满足并发需求,避免频繁创建和销毁线程的开销。

  • 最大线程数(maxPoolSize):最大线程数是线程池中允许存在的最大线程数量,它通常应该根据系统的硬件条件来设置,避免创建过多线程导致系统资源耗尽。

  • 拒绝策略的选择
    当线程池已满且无法继续处理新提交的任务时,我们需要选择适当的拒绝策略来处理这些任务。Java提供了几种默认的拒绝策略:

  1. ThreadPoolExecutor.AbortPolicy(默认)直接抛出RejectedExecutionException异常,拒绝新任务的提交。

  2. ThreadPoolExecutor.CallerRunsPolicy在调用线程中执行被拒绝的任务。也就是由调用execute方法的线程来执行该任务。

  3. ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待时间最长的任务,然后尝试提交新的任务。

  4. ThreadPoolExecutor.DiscardPolicy直接抛弃被拒绝的任务,不做任何处理。

可以根据具体应用场景选择合适的拒绝策略。

☀️ 6 并发编程的最佳实践

在这一部分,我们将总结一些Java多线程编程的最佳实践。我们会强调避免使用全局变量、尽量使用不可变对象以及选择合适的同步策略等重要原则。通过遵循这些最佳实践,我们能够写出更健壮、可维护的并发代码。


在Java多线程编程中,遵循一些最佳实践是非常重要的,可以帮助我们编写更健壮、可维护的并发代码。以下是一些Java多线程编程的最佳实践:

  1. 避免使用全局变量: 全局变量在多线程环境下容易造成竞态条件和数据不一致的问题。尽量避免使用全局变量,而是使用局部变量或者传递参数的方式来传递数据。

  2. 尽量使用不可变对象: 不可变对象是指对象在创建后其状态不能被修改。使用不可变对象可以避免线程安全问题,因为不可变对象不会被多个线程同时修改。

  3. 使用局部变量或线程安全容器: 如果需要在多个线程之间共享数据,使用局部变量或线程安全容器(如ConcurrentHashMap、ConcurrentLinkedQueue等)来保证线程安全性。

  4. 选择合适的同步策略: 使用synchronized关键字或Lock接口来保护共享资源的访问,选择合适的同步策略可以避免死锁和提高性能。

  5. 避免线程活跃性问题: 注意避免线程的活跃性问题,如死锁、饥饿等情况,合理设计线程的执行顺序和同步机制。

  6. 使用线程池: 使用线程池来管理和复用线程,避免频繁创建和销毁线程,提高程序性能和资源利用率。

  7. 使用原子类和并发集合: 使用Atomic包下的原子类和并发集合类可以简化多线程编程,并提供高效的线程安全操作。

下面是一个综合示例,展示了如何遵循上述最佳实践来编写一个线程安全的计数器:

import java.util.concurrent.atomic.AtomicInteger;public class ThreadSafeCounter {private final AtomicInteger count = new AtomicInteger(0);// 线程安全的计数方法public void increment() {count.incrementAndGet();}// 获取计数值public int getCount() {return count.get();}public static void main(String[] args) throws InterruptedException {final int NUM_THREADS = 5;final int NUM_INCREMENTS = 10000;ThreadSafeCounter counter = new ThreadSafeCounter();Thread[] threads = new Thread[NUM_THREADS];for (int i = 0; i < NUM_THREADS; i++) {threads[i] = new Thread(() -> {for (int j = 0; j < NUM_INCREMENTS; j++) {counter.increment();}});threads[i].start();}// 等待所有线程执行完毕for (Thread thread : threads) {thread.join();}System.out.println("Final count: " + counter.getCount());}
}

我们使用了AtomicInteger来实现线程安全的计数器。AtomicInteger是一个原子类,它保证了计数操作的原子性,避免了线程安全问题。通过使用原子类,我们避免了手动加锁的复杂性,并提高了程序的性能。

同时,我们在主线程中创建了多个线程来对计数器进行增加操作。在这个例子中,计数器的最终值将会是NUM_THREADS * NUM_INCREMENTS,并且由于使用了AtomicInteger,计数器是线程安全的。


🌄 7 总结

通过学习,我们已经掌握了Java多线程编程与并发控制的核心概念和技巧。多线程编程是Java中必不可少的一部分,它能够充分利用现代计算机多核处理器的性能,实现高效并发处理。然而,多线程编程也伴随着一些挑战和风险,例如死锁、竞态条件等。通过灵活运用线程同步、并发集合类和线程池等工具,以及遵循并发编程的最佳实践,我们能够优雅地驾驭多线程,构建出高性能、可靠的Java应用。

建议:不熟悉的多线程尽量不要使用(有一定的底蕴再去使用),“高效使用多线程:避免滥用与死锁,合理设置优先级与变量共享”

如对本文内容有任何疑问、建议或意见,请联系作者,作者将尽力回复并改进📓;(联系微信:Solitudemind )

相关文章:

⚡️⚡️Java多线程编程的高效、安全实践

⚡️ Java多线程编程的高效、安全实践⚡️ ☀️ 1 摘要☀️2 多线程编程基础☀️ 3 线程同步与互斥☀️ 4 并发集合类与原子操作☀️ 5 线程池与执行器框架☀️ 6 并发编程的最佳实践&#x1f304; 7 总结 博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客&#x1…...

【云原生】Docker私有仓库registry

目录 1&#xff09;用docker容器运行registry私有仓库服务。 2&#xff09;运行私有仓库服务 3&#xff09;镜像重命名(要上传的镜像名需要注明私仓的ip) 4&#xff09;编辑docker配置文件(因为默认是拉取docker官方的镜像&#xff0c;需要重新指定) 5&#xff09;其他dock…...

第十四届蓝桥杯大赛青少年省赛C++组试题真题 2023年5月

一、选择题 第 1 题 单选题 C中&#xff0c;bool类型的变量占用字节数为 ( )。 A. 1 B. 2 C. 3 D. 4 第 2 题 单选题 以下关于C结构体的说法&#xff0c;正确的是 ( )。 A. 结构体中只能包含成员变量&#xff0c;不能包含成员函数 B. 结构体不能从另一个结构体继承 …...

GAN论文精读

标题:Generative Adversarial Nets 摘要: 简写:作者提出了一个framework通过一个对抗的过程&#xff0c;在这里面会同时训练两个模型。 第一个模型为生成模型G&#xff0c;是用来抓住整个数据的分布 第二个模型为辨别模型D&#xff0c;是用来估计一个样本是否从G中产生。 …...

数据结构:计数排序(详解)

思路详解&#xff1a; 1 找到数组中的最大值、最小值 2 开辟一个统计每个数据出现次数的数组&#xff08;总个数是最大值-最小值1&#xff0c;因为下标范围是0~最大值-最小值&#xff0c;闭区间统计个数要1&#xff09; 3 遇到一个元素&#xff0c;在此元素-最小值作为下标的…...

1 请使用js、css、html技术实现以下页面,表格内容根据查询条件动态变化。

1.1 创建css文件&#xff0c;用于编辑style 注意&#xff1a; 1.背景颜色用ppt的取色器来获取&#xff1a; 先点击ppt的形状轮廓&#xff0c;然后点击取色器&#xff0c;吸颜色&#xff0c;然后再点击形状轮廓的其他轮廓颜色&#xff0c;即可获取到对应颜色。 2.表格间的灰色线…...

react-native项目安卓版本升级 compileSdkVersion 29->31

因为 react-native-ble-manager添加过程及碰到的问题 依赖 https://github.com/innoveit/react-native-ble-manager 参考&#xff1a;https://blog.csdn.net/withings/article/details/71378562 iOS 按react-native-ble-manager 文档在 【Info.plist】加了key之后能正常使用…...

【学习笔记】目标跟踪领域SOTA方法比较

目录 前言方法1 TraDeS:2 FairMOT:3 SMILEtrack:4 ByteTrack: 前言 常用于行人跟踪的多目标跟踪数据集包括&#xff1a;MOT 15/16/17/20、PersonPath22等… 为更好比较现有SOTA算法的检测性能&#xff0c;本博客将针对在各数据集上表现较优的算法模型进行介绍。&#xff08;表…...

机器学习 深度学习编程笔记

sigmoid函数 def sigmoid(x):return 1.0 / (1np.exp((-x)))定义最小平方和损失函数 loss torch.nn.MSELoss()线性回归编程 如果不加噪音就成了正常的线性函数了&#xff0c;所以要加噪音。 torch.normal(0, 0.01, y.shape)torch.normal(0, 0.01, y.shape)是一个用于生成服从…...

18.背景轮播

背景轮播 html部分 <div class"container"><div class"slide active" style"background-image: url(./static/20180529205331_yhGyf.jpeg);"></div><div class"slide " style"background-image: url(./s…...

论文代码学习—HiFi-GAN(2)——鉴别器discriminator代码

文章目录 引言正文鉴别器多周期鉴定器多尺度鉴定器问题 总结 引言 这里翻译了HiFi-GAN这篇论文的具体内容&#xff0c;具体链接。这篇文章还是学到了很多东西&#xff0c;从整体上说&#xff0c;学到了生成对抗网络的构建思路&#xff0c;包括生成器和鉴定器。细化到具体实现的…...

Linux Shell 脚本编程学习之【第3章 正则表达式 (第二部分) grep命令】

第3章 正则表达式 &#xff08;第二部分&#xff09; 4 grep命令4.1 基本用法4.2 参考命令4.2.1 双引号4.2.2 -c 输出匹配行数4.2.3 -h 或 -l 不显示或只显示文件名4.2.4 -s 不显示错误信息4.2.5 -r 递归显示本级目录及下级目录4.2.6 -w 匹配完整词 -x 匹配完整行4.2.7 -q 退出…...

大语言模型LLM

目录 一、语言模型的发展 语言模型&#xff08;Language Model&#xff0c;LM&#xff09;目标是建模自然语言的概率分布&#xff0c;具体目标是构建词序列w1,w2,...,wm的概率分布&#xff0c;即计算给定的词序列作为一个句子出现可能的大小P(w1w2...wm)。但联合概率P的参数量…...

自学网络安全(黑客)的误区

前言 网络安全入门到底是先学编程还是先学计算机基础&#xff1f;这是一个争议比较大的问题&#xff0c;有的人会建议先学编程&#xff0c;而有的人会建议先学计算机基础&#xff0c;其实这都是要学的。而且这些对学习网络安全来说非常重要。 一、网络安全学习的误区 1.不要…...

@Conditional

Conditional Conditional 是 spring framework 中提供的一个条件注解&#xff0c;&#xff0c;满足条件就注入&#xff0c;不满足就不注入ioc Condtional 需要和 Condition接口 一起用&#xff1a; 返回true注入&#xff0c;返回false不注入&#xff0c;&#xff0c; 里面有一…...

【Linux】网络基础之TCP协议

目录 &#x1f308;前言&#x1f338;1、基本概念&#x1f33a;2、TCP协议报文结构&#x1f368;2.1、源端口号和目的端口号&#x1f369;2.2、4位首部长度&#x1f36a;2.3、32位序号和确认序号&#xff08;重点&#xff09;&#x1f36b;2.4、16位窗口大小&#x1f36c;2.5、…...

Java设计模式之装饰器(Decorator)模式

装饰器&#xff08;Decorator&#xff09;设计模式允许动态地将新功能添加到对象中&#xff0c;同时又不改变其结构。 什么是装饰器模式 装饰器&#xff08;Decorator&#xff09;模式通过将对象进行包装&#xff0c;以扩展其功能&#xff0c;而不需要修改其原始类。装饰器模…...

element ui树组件render-content 树节点的内容区的渲染另一种方式

直接上代码吧,不用h的写法。 <el-tree :data"data" node-key"id" default-expand-all :expand-on-click-node"false" :props"defaultProps":render-content"renderContentTree" node-click"handleNodeClick"&g…...

html a标签换行显示

文章目录 用css display属性不用css&#xff0c;可以用<br>标签换行示例 用css display属性 可以使用CSS的display属性来实现多个a标签每行显示一个。 HTML代码&#xff1a; <div class"link-container"><a href"#">Link 1</a>…...

关于Redis-存Long取Integer类型转换错误的问题

背景 最近遇到了两个Redis相关的问题&#xff0c;趁着清明假期&#xff0c;梳理整理。 1.存入Long类型对象&#xff0c;在代码中使用Long类型接收&#xff0c;结果报类型转换错误。 2.String对象的反序列化问题&#xff0c;直接在Redis服务器上新增一个key-value&#xff0c…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

Webpack性能优化:构建速度与体积优化策略

一、构建速度优化 1、​​升级Webpack和Node.js​​ ​​优化效果​​&#xff1a;Webpack 4比Webpack 3构建时间降低60%-98%。​​原因​​&#xff1a; V8引擎优化&#xff08;for of替代forEach、Map/Set替代Object&#xff09;。默认使用更快的md4哈希算法。AST直接从Loa…...