JavaEE 初阶篇-深入了解 CAS 机制与12种锁的特征(如乐观锁和悲观锁、轻量级锁与重量级锁、自旋锁与挂起等待锁、可重入锁与不可重入锁等等)

🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍

文章目录

        1.0 乐观锁与悲观锁概述

        1.1 悲观锁(Pessimistic Locking)

        1.2 乐观锁(Optimistic Locking)

        1.3 区别与适用场景

        2.0 轻量级锁与重量级锁概述

        2.1 真正加锁的底层逻辑顺序

        2.2 轻量级锁

        2.3 重量级锁

        2.4 区别于适用场景

        2.5 轻量级锁与乐观锁的区别、重量级锁与悲观锁的区别

        3.0 自旋锁与挂起等待锁概述

        3.1 自旋锁(Spin Lock)

        3.2 挂起等待锁(Suspend-Resume Lock)

        3.3 自旋锁是一种典型的轻量级锁的实现方式

        4.0 公平锁与非公平锁概述

        4.1 公平锁(Fair Lock)

        4.2 非公平锁(Unfair Lock)

        5.0 可重入锁与不可重入锁概述

        6.0 读写锁与互斥锁概述

        6.1 读写锁(Read-Write Lock)

        6.2 互斥锁(Mutex Lock)

        7.0 CAS 概述

        7.1 CAS 的实际应用

        7.1.1 CAS 的实际应用 - 实现原子类

        7.1.2 CAS 的实际应用 - 自旋锁


        1.0 乐观锁与悲观锁概述

        乐观锁和悲观锁是两种并发控制的策略,用于处理多线程环境下的数据访问和更新。它们的主要区别在于对并发情况的预期和处理方式。

        synchronized 是乐观锁也悲观锁。

        1.1 悲观锁(Pessimistic Locking)

        悲观锁的基本思想是在操作数据之前先获取锁,假定会并发访问,因此在整个操作过程中都持有锁,以防其他线程对数据进行修改。悲观锁通常会导致其他线程在访问数据时被阻塞,以确保数据的一致性。

        简单来说,在多线程中对共享变量进行操作时,会认为其他线程都会对这个共享变量进行操作,因此,从悲观锁的角度来说,先加锁成功后,才能对共享变量进行操作;否则,只能阻塞等待锁释放。从而可以确保线程安全。

        常见的悲观锁:synchronized 关键字、ReentrantLock 等锁机制。

        1.2 乐观锁(Optimistic Locking)

        乐观锁的基本思想是假设在数据操作过程中不会发生并发冲突,因此不会立即加锁,而是在更新数据时检查是否有其他线程已经对数据进行修改了。如果没有发现数据被修改,那么继续操作;如果发现数据已经被修改了,会进行回滚或者重试。

        简单来说,在多线程中对共享变量进行操作时,一开始不会认为有其他线程会对该共享变量进行操作,认为当前线程可以安心的对该变量进行操作。执行到后面,如果发现已经有其他线程对该共享变量进行了操作了,那么当前的线程执行回滚或者重试。

        1.3 区别与适用场景

        synchronized 一开始是使用乐观锁策略,当发现锁竞争比较频繁的时候,就会自动切换悲观锁策略。

        悲观锁:真正加上了锁处理,适用于并发写入较多、冲突概率较高的场景,适合长事务处理。

        乐观锁:没有真正的加锁处理,适用于并发读取较多,冲突概率较低的场景,适合短事务处理。

        2.0 轻量级锁与重量级锁概述

        轻量级锁和重量级锁是 Java 中用于实现同步的两种锁机制,用于保护共享资源在多线程环境下的访问。它们的设计目的是为了在不同情况下提供更高效的并发控制。

        synchronized 是轻量级锁也是重量级锁。

        2.1 真正加锁的底层逻辑顺序

        CPU 提供了原子操作指令给操作系统,操作系统提供了 mutex 互斥锁给 JVM ,接着 JVM 将锁封装成了 synchronized 中的悲观锁、ReenTrantLock 等。

        2.2 轻量级锁

        加锁机制尽可能不适用 mutex ,而是尽量在用户态代码中完成,实在锁竞争太大、锁冲突太大了,再转换为重量级,即使用 mutex 。

        轻量级锁是一种乐观锁机制,用于优先低竞争情况下的同步操作。当一个线程尝试获取锁是,如果锁没有被其他线程占用,会将对象的 Mark Word 指向当前线程,将对象状态标记为“偏向锁”。

        2.3 重量级锁

        加锁机制重度依赖了 OS 提供了 mutex ,大量的内核态用户态切换,很容易引发线程的调度。两个操作成本比较高,一旦涉及到用户态和内核态的切换,就意味着“沧海桑田”。

        重量级锁是一种悲观锁机制,用于处理高竞争情况下的同步操作。当多个线程竞争同一把锁时,会将锁升级为重量级锁,线程会被阻塞,进入阻塞状态。

        2.4 区别于适用场景

        synchronized 开始是一个轻量级锁。如果锁冲突比较严重,就会变成重量级锁。

        轻量级锁:适用于低竞争情况下的同步操作,提高了并发性能,但在高竞争情况下会升级为重量级锁。

        重量级锁:适用于高竞争情况下的同步操作,保证了数据的一致性,但在低竞争情况下会带来额外的开销。

在实际应用中,Java虚拟机会根据当前线程的竞争情况动态地选择轻量级锁或重量级锁来进行同步操作,以提高系统的并发性能和数据一致性。

        2.5 轻量级锁与乐观锁的区别、重量级锁与悲观锁的区别

轻量级锁:

        轻量级锁是一种乐观锁,它尝试使用 CAS(Compare and Swap)等原子操作来尝试获取锁,避免了线程阻塞和内核态操作,因此被称为轻量级。如果 CAS 操作成功,线程就成功获取了锁,如果失败,则会升级为重量级锁或其他适合的锁机制。轻量级锁并不是没有真正的加锁,而是通过乐观的方式尝试获取锁,避免了一些开销较大的操作。

重量级锁:

        重量级锁是一种悲观锁,它通常会涉及到线程的阻塞、唤醒和操作系统的调度等操作,因此被称为重量级。当多个线程竞争锁时,重量级锁会导致线程进入阻塞状态,等待其他线程释放锁后才能继续执行。重量级锁会涉及到真正的加锁操作,包括线程的阻塞和唤醒等。

        3.0 自旋锁与挂起等待锁概述

        自旋锁和挂起等待锁是两种不同的锁机制,它们在处理线程同步和互斥时有不同的实现方式和特点。

        synchronized 是自旋锁也是挂起锁。

        3.1 自旋锁(Spin Lock)

        自旋锁是一种基于忙等待的锁机制,当一个线程尝试获取锁时,如果发现锁已经被其他线程占用,它会一直循环检查锁的状态(自旋)直到锁可用。自旋锁适用于锁被占用时间较短的情况,因为它可用减少线程切换的开销。但是如果锁被长时间占用,自旋锁会导致线程长时间占用 CPU 资源而无法进展,造成性能问题。

        简单来说,自旋锁一直会占用 CPU 资源,所谓的“空转”、“忙等待”,只要锁被释放了,那么自旋锁就会立马获取锁,效率高。

        按照之前的方式,线程再抢锁失败后,进入阻塞状态,放弃 CPU ,需要过很久再次被调度,但实际上,大部分情况下,虽然当前线程抢锁失败,但过不了多久,锁就会被释放。这样就没有必要放弃 CPU 资源。这时候就可以适用自旋锁来处理这样的问题。

        3.2 挂起等待锁(Suspend-Resume Lock)

        挂起等待锁是一种基于线程阻塞和唤醒的锁机制,当一个线程尝试获取锁时,如果发现锁已经被占用,它会被挂起阻塞等待其他线程释放锁。当锁可用时,其他线程会唤醒被挂起的线程继续执行。

        挂起等待锁适用于锁被占用时间长的情况,因为它可以避免线程忙等待占用 CPU 资源,但是会引入线程切换的开销。

        3.3 自旋锁是一种典型的轻量级锁的实现方式

        synchronized 中的轻量级锁策略大概率就是通过自旋锁的方式实现的。

优点:没有放弃 CPU ,不涉及线程阻塞和调度,一旦锁被释放,就能第一时间获取到锁。

缺点:如果锁被其他线程持有的时间比较长,那么就会持续的消耗 CPU 资源。(而挂起等待的时候是不会消耗 CPU 资源的)。

        相对应的,挂起等待锁是一种典型的重量级锁的实现方式。

        4.0 公平锁与非公平锁概述

        公平锁和非公平锁是两种不同的锁策略,它们主要影响了锁的获取顺序和公平性。

        synchronized 是非公平锁。ReenTrantLock 默认是非公平锁,可以转换成公平锁。

ReentrantLock fairLock = new ReentrantLock(true); // 创建公平锁

        通过将参数设置为 true,可以创建一个公平锁;而默认情况下参数为 false,创建的是非公平锁。

        4.1 公平锁(Fair Lock)

        公平锁是一种保证锁的获取按照请求的顺序进行的锁。当一个线程请求一个公平锁时,如果锁当前被其他线程占用,该线程会进入等待队列,按照先来先服务的原则等待获取锁。当锁释放时,等待时间最长的线程会被唤醒并获取锁。公平锁能够保证线程按照请求的顺序获取锁,避免了线程饥饿的问题。

        4.2 非公平锁(Unfair Lock)

        非公平锁是一种允许锁获取竞争策略,它允许新请求的线程直接尝试获取锁,而不考虑等待队列中的线程顺序。如果锁当前被其他线程占用,新请求的线程会直接尝试获取锁,而不会进入等待队列。这种策略可能会导致某些线程长时间无法获取锁,造成线程饥饿的问题。

        5.0 可重入锁与不可重入锁概述

        可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。比如一个递归函数里面加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入锁(因为这个原因可重入锁也叫做递归锁)。

        Java 里只要以 ReenTrant 开头命名的锁都是可重入锁,而且 JDK 提供的所有现成的 Lock 实现类,包括 synchronized 关键字锁都是可重入锁。

        而 Linux 系统提供的 mutex 是不可重入锁。

        6.0 读写锁与互斥锁概述

        读写锁简单来说,读操作与读操作并发执行中不会加锁,读操作与写操作并发操作中会加锁,写操作与写操作并发操作中会加锁。

        互斥锁简单来说,无论进行哪一种操作,并发执行的操作都需要加上锁。

        synchronized 不是读写锁,是互斥锁。

        ReenTrantLock 不是读写锁,是互斥锁。

ReenTrantLock 使用代码演示:

import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private final ReentrantLock lock = new ReentrantLock();private int sharedData = 0;public void incrementData() {lock.lock();try {sharedData++;} finally {lock.unlock();}}public static void main(String[] args) {ReentrantLockExample example = new ReentrantLockExample();// 创建多个线程并发执行 incrementData 方法Thread thread1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {example.incrementData();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {example.incrementData();}});thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Final shared data value: " + example.sharedData);}
}

        6.1 读写锁(Read-Write Lock)

        读写锁允许多个线程同时读取共享资源,但在有写操作时需要互斥访问。

读操作:

        多个线程可以同时获取读锁,并发执行读操作,不会互斥。

写操作:

        写锁是互斥的,即写操作与任何其他操作或写操作都是互斥的。当有线程持有写锁时,其他线程无法获取读锁或者写锁,直到写操作释放写锁。

        ReentrantReadWriteLock.ReadLock 类表示一个读锁,这个对象提供了 lock/unlock 方法进行加锁解锁。

        ReentrantReadWriteLock.WriteLock 类表示一个写锁,这个对象提供了 lock/unlock 方法进行加锁解锁。

代码如下:

import java.util.concurrent.locks.ReentrantReadWriteLock;public class MyReentrantLock {private int data = 100;private final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();private final ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();private final ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();public int readData(){readLock.lock();try {return data;}finally {readLock.unlock();}}public void writeData(int data){writeLock.lock();try {this.data = data;}finally {writeLock.unlock();}}
}
public class demo1 {public static void main(String[] args) {MyReentrantLock myReentrantLock = new MyReentrantLock();//读取数据for (int i = 0; i < 100; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName() + "-->读取数据为: " + myReentrantLock.readData());}).start();}//写数据for (int i = 0; i < 100; i++) {new Thread(()->{myReentrantLock.writeData(1);}).start();}for (int i = 0; i < 100; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName() + "-->读取数据为: " + myReentrantLock.readData());}).start();}}
}

        6.2 互斥锁(Mutex Lock)

        互斥锁是一种常见的锁机制,用于保护共享资源的互斥访问。无论是读操作还是写操作,都要获取互斥锁才能访问共享资源。

        7.0 CAS 概述

        CAS(Compare and Swap)是一种并发控制机制,通常用于实现无锁算法。它主要用于解决多线程并发访问共享数据时的原子性操作问题。

        CAS 的基本原理是利用 CPU 提供的原子性指令来实现无锁的原子操作。当多个线程同时尝试执行 CAS 操作时,只有一个线程会成功,其他线程会失败并重试。

CAS 操作包括三个步骤:

        1)比较(Compare):首先, CAS 会比较当前内存中的值和预期值是否相等。

        2)交换(Swap):如果相等,CAS 就会将新值写入到主内存中;否则,不做任何操作。

        3)返回(Return):CAS 操作会返回操作是否成功的结果,通常时一个布尔值。

        这三个操作都是原子性的,因此不存在线程安全问题。

图解:

        7.1 CAS 的实际应用

        原子操作、非阻塞算法、自旋锁、ABA 问题的解决、乐观锁的实现等。

        7.1.1 CAS 的实际应用 - 实现原子类

        CAS 可以用于实现原子操作,比如 AtomicInteger、AtomicLong 等原子类都是基于 CAS 实现的。在多线程环境下,通过 CAS 可以确保对共享变量的操作是原子的,避免了使用锁带来的性能开销。

        比如说 AtomicInteger 类,是基于 CAS 的思想实现的,假如有一个 AtomicInteger 的实例对象,在多线程中,对该实例对象进行 +1 操作,一般来说,如果不加锁的话,会出现线程安全问题,但是对于当前对象来说,即使不用加锁,也不用出现线程安全问题。

代码如下:

import java.util.concurrent.atomic.AtomicInteger;public class MyAtomicInteger {private AtomicInteger count = new AtomicInteger(0);public void add(){System.out.println(count.incrementAndGet());}
}
public class Text {public static void main(String[] args) {MyAtomicInteger myAtomicInteger = new MyAtomicInteger();for (int i = 0; i < 1000; i++) {new Thread(()->{for (int j = 0; j < 5000; j++) {myAtomicInteger.add();}}).start();}}
}

运行结果如下:

        运行结果是正确的,没有出现线程安全问题。

        这是为什么即使没有加上锁也不会出现线程安全问题呢?

        答案就在用了 AtomicInteger 修饰的变量,且 +1 操作用到了 count.incrementAndGet() 实例方法。

详细对 count.incrementAndGet() 方法进行分析:

        该方法中还包含了 getAndAddInt() 方法,第一个参数是代表着当前对象,第二个参数可以认为是存放值的地址,第三个参数默认为 1 。

进入 getAndAddInt() 方法进行分析:

         参数 o 代表着当前对象,参数 offset 代表着值的地址,参数 delta 为 1 。该方法中内部定义了一个变量 v ,通过 getIntVolatile() 这个方法,用当前的对象还有值的地址获取到最新的数据赋值给 v 。再接着通过 weakCompareAndSetInt() 方法,来比较当前的 v 跟之前获取的 v 的值是否相同,如果相同,代表着没有线程访问这个数据,只有当前线程正在访问,那么就可以对这个数据进行修改,再返回到主内存中;如果不相同,代表有其他线程访问这个数据,此时不能直接将当前线程更新的值放到主内存中,会出现线程安全问题,因此重复循环,再来新一轮,先获取主内存中最新的数据,在来比较当前数据与之前获取到的 v 是否相同...一直循环往复。直到当前数据与之前的获取到的 v 相同,那么就可以将值放入到内存中。

进入 weakCompareAndSetInt() 方法进行分析:

        如果成功就返回 true,否则返回 false 。 

        最后,可以清楚的了解到以上这个思想跟 CAS 的机制是一致的。

        7.1.2 CAS 的实际应用 - 自旋锁

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

伪代码:

public class MySpinLock {private  Thread ower = null;public void lock(){//通过 CAS 看当前锁是否被某个线程持有//如果这个锁已经被别的线程持有,那么就自旋等待。//如果这个锁没有被别的线程持有,那么就把 ower 设为当前尝试加锁的线程。while (!CAS(this.ower,null,Thread.currentThread())){}}public void unlock(){this.ower = null;}
}

        结合自旋锁的特点和 CAS 机制来分析,线程只要没有获取的锁,就会一直占用 CPU 资源等待,直到锁释放为止,如何来判断锁是否被占用呢?

        就可以通过 CAS 机制来判断,大概流程是:判断当前的线程 ower 是否为 null ,如果是,则将 ower 修改为当前线程所持有,这样来看,其他线程也会通过 CAS 机制来判断当前的 ower 是否否为 null ,返回结果为 false ,则只能空转了,等待当前线程释放锁,此时释放锁会把 ower 赋值为 null 。交给其他线程来获取这把“锁”。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/306409.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IPSec简介

起源 随着Internet的发展&#xff0c;越来越多的企业直接通过Internet进行互联&#xff0c;但由于IP协议未考虑安全性&#xff0c;而且Internet上有大量的不可靠用户和网络设备&#xff0c;所以用户业务数据要穿越这些未知网络&#xff0c;根本无法保证数据的安全性&#xff0…

Docker 安装 Linux 系统可视化监控 Netdata

docker 安装 netdata 前提准备Docker 两种方式部署 Netdata1、使用 docker run 命令运行 netdata 服务2、使用 docker compose 运行 netdata 服务 Netdata 服务可视化界面Netdata 汉化处理 前提准备 说明&#xff1a;此处使用 windows11 安装的 docker desktop & wsl2/apli…

10-热点文章-定时计算

xxl-Job分布式任务调度 1 今日内容 1.1 需求分析 目前实现的思路&#xff1a;从数据库直接按照发布时间倒序查询 问题1&#xff1a; 如何访问量较大&#xff0c;直接查询数据库&#xff0c;压力较大 问题2&#xff1a; 新发布的文章会展示在前面&#xff0c;并不是热点文章 …

halcon 两图叠加 显示

halcon 两图叠加 显示 代码 read_image (Image, E:/test/CameraWeldHead/二号机焊头图片.bmp) read_image (Image1, E:/test/CameraWeldHead/右1.bmp) mirror_image (Image1, ImageMirror, column)crop_part (Image, ImagePart1, 0, 0, 4096, 4096) crop_part (ImageMirror, …

【面试题】细说mysql中的各种锁

前言 作为一名IT从业人员&#xff0c;无论你是开发&#xff0c;测试还是运维&#xff0c;在面试的过程中&#xff0c;我们经常会被数据库&#xff0c;数据库中最经常被问到就是MySql。当面试官问MySql的时候经常会问道一个问题&#xff0c;”MySQL中有哪些锁&#xff1f;“当我…

LeetCode - 1702. 修改后的最大二进制字符串

文章目录 解析AC CODE 题目链接&#xff1a;LeetCode - 1702. 修改后的最大二进制字符串 解析 详细题解&#xff1a;贪心&#xff0c;简洁写法&#xff08;Python/Java/C/Go/JS/Rust&#xff09; 思路很牛b。 简单来说我们需要想办法将0配对&#xff0c;将其变为10&#xff0…

深度学习【向量化(array)】

为什么要向量化 在深度学习安全领域、深度学习练习中&#xff0c;你经常发现在训练大量数据时&#xff0c;深度学习算法表现才更加优越&#xff0c;所以你的代码运行的非常快至关重要&#xff0c;否则&#xff0c;你将要等待非常长的时间去得到结果。所以在深度学习领域向量化…

C/S医学检验LIS实验室信息管理系统源码 医院LIS源码

LIS系统即实验室信息管理系统。LIS系统能实现临床检验信息化&#xff0c;检验科信息管理自动化。其主要功能是将检验科的实验仪器传出的检验数据经数据分析后&#xff0c;自动生成打印报告&#xff0c;通过网络存储在数据库中&#xff0c;使医生能够通过医生工作站方便、及时地…

如何卸载干净 IDEA(图文讲解)

更新时间 2022-12-20 11:一则或许对你有用的小广告 星球 内第一个项目&#xff1a;全栈前后端分离博客项目&#xff0c;演示地址&#xff1a;Weblog 前后端分离博客, 1.0 版本已经更新完毕&#xff0c;正在更新 2.0 版本。采用技术栈 Spring Boot Mybatis Plus Vue 3.x Vit…

Android开发基础:对话框,Toast,Notification的使用 选项菜单,上下文菜单,弹出式菜单的使用

目录 一&#xff0c;Android提示消息 1.提示消息的形式 2.对话框 &#xff08;1&#xff09;默认对话框的创建步骤 &#xff08;2&#xff09; 自定义对话框的创建步骤 3.Toast 4.Notification 二&#xff0c;菜单 1.选项菜单 OptionsMenu 2.上下文菜单 ContextMenu …

【python】在pycharm创建一个新的项目

双击打开pycharm,选择create new project 选择create,后进入项目 右键项目根目录,选择new一个新的python file 随意命名一下 输入p 然后后面就会出现智能补全提示,此时轻敲一下tab,代码就写好了,非常的方便 右键执行一下代码,下面两个直接运行和debug运行都是可以的 小结 …

Leetcode - 周赛392

目录 一&#xff0c;3105. 最长的严格递增或递减子数组 二&#xff0c;3106. 满足距离约束且字典序最小的字符串 三&#xff0c;3107. 使数组中位数等于 K 的最少操作数 四&#xff0c;3108. 带权图里旅途的最小代价 一&#xff0c;3105. 最长的严格递增或递减子数组 本题求…

基于Whisper语音识别的实时视频字幕生成 (二): 在线实时字幕

Whisream Whistream&#xff08;微流&#xff09;是基于Whisper语音识别的的在线字幕生成工具&#xff0c;支持rtsp/rtmp/mp4等视频流在线语音识别 1. whistream介绍 whistream将在whishow基础上引入whisper进行在线语音识别生成视频字幕 2. 使用 python&#xff1a; pyth…

Python爬取链家数据

技术&#xff1a;requests、BeautifulSoup、SQLite 解析页面&#xff0c;存数据到SQLite数据库&#xff0c;到时候你用navicat导出成csv什么的就行 1、确定城市 以天津为例&#xff0c;网页是https://tj.lianjia.com/ershoufang/rs/ 把上面这些地区名字复制 2、爬取数据内容…

题目 2694: 蓝桥杯2022年第十三届决赛真题-最大数字【暴力解法】

最大数字 原题链接 &#x1f970;提交结果 思路 对于每一位&#xff0c;我我们都要尽力到达 9 所以我们去遍历每一位, 如果是 9 直接跳过这一位 如果可以上调到 9 我们将这一位上调到 9 &#xff0c;并且在a 中减去对应的次数 同样的&#xff0c;如果可以下调到 9&#xff0c;我…

MongoDB的安装和使用

1.MongoDB 安装 1.1 基于Docker安装 docker run --restartalways -d --name mongo -v /opt/mongodb/data:/data/db -p 27017:27017 mongo:4.0.6 1.2 客户端工具使用 MongoDB Compass | MongoDB 2.MongoDB 使用 2.1 引用依赖包 <dependency><groupId>org.sprin…

YOLOV5 分类:利用yolov5进行图像分类

1、前言 之前介绍了yolov5的目标检测示例,这次将介绍yolov5的分类展示 目标检测:YOLOv5 项目:训练代码和参数详细介绍(train)_yolov5训练代码的详解-CSDN博客 yolov5和其他网络的性能对比 yolov5分类的代码部分在这 2、数据集准备 yolov5分类的数据集就是常规的摆放方式…

TypeScript 中文错误消息

TypeScript 本身支持多语言显示错误消息&#xff0c;默认会追随操作系统或开发工具选择一个显示错误消息的语言。如果我们想强制让其显示某种语言可以如下设置&#xff0c;例如强制显示中文 命令行输入如下 npx tsc --locale zh-CN 对于 vsCode 中的错误消息可如下设置 设置…

[C++][算法基础]模拟散列表(哈希表)

维护一个集合&#xff0c;支持如下几种操作&#xff1a; I x&#xff0c;插入一个整数 x&#xff1b;Q x&#xff0c;询问整数 x 是否在集合中出现过&#xff1b; 现在要进行 N 次操作&#xff0c;对于每个询问操作输出对应的结果。 输入格式 第一行包含整数 N&#xff0c;…

macU盘在电脑上读不出来 u盘mac读不出来怎么办 macu盘不能写入 Tuxera NTFS for Mac免费下载

对于Mac用户来说&#xff0c;使用U盘是很常见的操作&#xff0c;但有时候可能会遇到Mac电脑无法读取U盘的情况&#xff0c;这时候就需要使用一些特定的工具软件来帮助我们解决问题。本文就来告诉大家macU盘在电脑上读不出来是怎么回事&#xff0c;u盘mac读不出来怎么办。 一、m…