3. Java中的锁

文章目录

  • 乐观锁与悲观锁
    • 乐观锁(无锁编程,版本号机制)
    • 悲观锁
    • 两种锁的伪代码比较
  • 通过 8 种锁运行案例,了解锁
    • 锁相关的 8 种案例演示
      • 场景一
      • 场景二
      • 场景三
      • 场景四
      • 场景五
      • 场景六
      • 场景七
      • 场景八
    • synchronized 有三种应用方式
      • 8 种锁的案例实际体现在 3 个地方
    • 从字节码角度分析 synchronized 实现
      • `javap -c ****.class`文件反编译
      • synchronized 同步代码块
      • synchronized 普通同步方法
      • synchronized 静态同步方法
    • 对于 synchronized 的深入研究
      • 面试题:为什么任何一个对象都可以成为一个锁
      • 什么是管程 monitor
  • 公平锁与非公平锁
    • 为什么会有公平锁和非公平锁的设计?
    • 为什么默认使用非公平锁?
    • 什么时候用公平锁?什么时候用非公平锁?
  • 可重入锁,又叫,递归锁
    • 隐式锁(synchronized 默认是可重入锁)
      • 同步块
      • 同步方法
    • synchronized 的可重入原理(基于 objectMonitor.hpp)
    • 显示锁(Lock,ReentrantLock)
  • 死锁以及排查
    • 死锁是什么
    • 编写一个死锁 case
    • 死锁的故障排查
  • 小结
  • 见后续
    • 自旋锁 SpinLock
    • 无锁->独占锁->读写锁->邮戳锁
    • 无锁->偏向锁->轻量锁->重量锁

乐观锁与悲观锁

乐观锁(无锁编程,版本号机制)

  • 认为自己在使用数据时,不会有别的线程修改数据或资源,所以不会加锁 。
  • 在 Java 中通过使用无锁编程来实现,只在更新数据时去判断,之前是否存在其它线程更新此数据。
    • 如果这个数据没有被更新,当前线程将自己修改的数据成功写入
    • 如果这个线程数据已经被其他线程更新,则根据不同的实现方式执行不同的操作
      • 放弃修改,尝试抢锁等
  • 判断规则
    • 版本号机制 Version
    • 最常采用的是 CAS 算法,Java 原子类的递增操作就通过 CAS 自旋实现的
  • 适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升
  • 乐观锁直接去操作同步资源,是一种无锁算法
  • 乐观锁的两种实现方式
    • 采用 Version 版本号机制
    • CAS(Compare-and-Swap,比较替换算法) 实现

悲观锁

  • 认为自己在使用数据时,必然有别的线程来修改数据,因此在获取到数据的时候,进行操作之前,会先加锁,保证数据不被别的线程所修改
  • synchronized 关键字与 Lock 锁的实现类均为悲观锁
  • 适合写操作多的场景,先加锁可以保证写操作时数据正确,
  • 显示锁定之后再进行同步资源

两种锁的伪代码比较

  • 悲观锁
 //悲观锁基于synchronized关键字public synchronized void m1(){//code logic segment....}//悲观锁基于Lock对象实现ReentrantLock reentrantLock = new ReentrantLock();public void m2(){reentrantLock.lock();try {//code logic segment....} finally {reentrantLock.unlock();}}
  • 乐观锁
        //乐观锁的调用方式,保证多个线程使用的是同一个AtomicIntegerAtomicInteger atomicInteger = new AtomicInteger();atomicInteger.incrementAndGet();

通过 8 种锁运行案例,了解锁

锁相关的 8 种案例演示

场景一

a,b 两个线程分别使用同步监视器修饰
a 线程启动,0.2 秒后 b 线程启动
可以预见,先执行 a 后执行 b

  • 代码
class Phone {//资源类public synchronized void sendEmail() {System.out.println(Thread.currentThread().getName() + "-----sendEmail");}public synchronized void sendSMS() {System.out.println(Thread.currentThread().getName() + "-----sendSMS");}
}...
public static void main(String[] args) {//一切程序的入口Phone phone = new Phone();new Thread(() -> {phone.sendEmail();}, "a").start();//暂停毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {phone.sendSMS();}, "b").start();}
  • 效果

image.png

场景二

在场景一的资源类中,sendEmail方法中加入暂停3秒钟
由于 synchronized 是悲观锁,并且 sleep 不回释放锁,因此 a 线程先执行,并且执行 sleep 时程序也会阻塞,当 a 线程执行完毕时,b 线程才会执行
可以预见结果和场景一致

  • 代码
    public  synchronized void sendEmail() {try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "-----sendEmail");}
  • 效果

image.png

一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一的一个线程去访问这些synchronized方法
锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法

场景三

添加一个普通的hello方法,先打印邮件还是hello?
普通方法线程共享

class Phone {//资源类....public void hello() {System.out.println("-------hello");}....
}//main方法public static void main(String[] args) {//一切程序的入口Phone phone = new Phone();new Thread(() -> {phone.sendEmail();}, "a").start();//暂停毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {phone.hello();}, "b").start();}
  • 效果

image.png

场景四

有两部手机,请问先打印邮件还是短信
两次方法的调用 synchronized 锁住的对象不同

  • 代码
//资源类不变public static void main(String[] args) {//一切程序的入口Phone phone = new Phone();Phone phone2 = new Phone();new Thread(() -> {phone.sendEmail();}, "a").start();//暂停毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {phone2.sendSMS();}, "b").start();}
  • 效果

image.png

场景五

在场景 一的情况下将两个方法均添加 static 修饰,测试代码同场景一
静态同步方法(类锁)

  • 代码
    public static synchronized void sendEmail() {try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "-----sendEmail");}public static synchronized void sendSMS() {System.out.println(Thread.currentThread().getName() + "-----sendSMS");}
  • 效果

image.png

场景六

在场景五的情况下,添加一部手机,用 phone2 调用 sentSMS

  • 代码
//资源类同场景五
//测试代码public static void main(String[] args) {//一切程序的入口Phone phone = new Phone();Phone phone2 = new Phone();new Thread(() -> {phone.sendEmail();}, "a").start();//暂停毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {phone2.sendSMS();}, "b").start();}
  • 效果

image.png

对于普通同步方法,锁的是当前实例对象,通常指this,具体的一部部手机,所有的普通同步方法用的都是同一把锁——>实例对象本身(方法调用者),
对于静态同步方法,锁的是当前类的Class对象,如Phone.class唯一的一个模板(调用者所属的类型)
对于同步方法块,锁的是 synchronized 括号内的对象

场景七

有1个静态同步方法,有1个普通同步方法,有1部手机,请问先打印邮件还是短信

    //将短信方法还原为普通同步方法public static synchronized void sendSMS() {System.out.println(Thread.currentThread().getName() + "-----sendSMS");}
  • 测试代码同场景一
    public static void main(String[] args) {//一切程序的入口Phone phone = new Phone();new Thread(() -> {phone.sendEmail();}, "a").start();//暂停毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {phone.sendSMS();}, "b").start();}
  • 效果

image.png

类锁与对象锁不是同一个,各种执行,a 线程睡眠 3 秒

场景八

有1个静态同步方法,有1个普通同步方法,有2部手机,请问先打印邮件还是短信

//资源类同场景七
//测试代码public static void main(String[] args) {//一切程序的入口Phone phone = new Phone();Phone phone2 = new Phone();new Thread(() -> {phone.sendEmail();}, "a").start();//暂停毫秒,保证a线程先启动try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {phone2.sendSMS();}, "b").start();}
  • 效果

image.png

当一个线程试图访问同步代码时它首先必须得到锁,正常退出或抛出异常时必须释放锁。
所有的普通同步方法用的都是同一把锁——实例对象本身,就是new出来的具体实例对象本身,本类this
也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁。
所有的静态同步方法用的也是同一把锁——类对象本身,就是我们说过的唯一模板Class
具体实例对象this和唯一模板Class,这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞态条件的
但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。

synchronized 有三种应用方式

8 种锁的案例实际体现在 3 个地方

  • 作用于实例方法,为当前方法调用者加锁,进入同步代码前需要获得当前实例的锁
  • 作用于代码块,synchronized(obj){},obj 为加锁对象
  • 作用于静态方法,当前类加锁,进入同步代码块之前需要获取类对象的锁

从字节码角度分析 synchronized 实现

javap -c ****.class文件反编译

  • -c 作用: 对代码进行反编译
  • -v (verbose) 作用 输出附加信息(行号,本地变量表,反编译等详细信息)

synchronized 同步代码块

  • 编写测试代码->运行产生 class 文件->进入 class 类路径–>执行 javap -c
Object object = new Object();public void m1() {synchronized (object) {System.out.println("----hello synchronized code block");}}
  • 反编译结果
    • 一般情况下,一个 enter 对应 2 个 exit

image.png

  • 极端情况==>手动抛出一个异常
Object object = new Object();public void m1() {synchronized (object) {System.out.println("----hello synchronized code block");throw new RuntimeException("-----exp");}
}

image.png

  • synchronized 同步代码块的实现
    • 进入锁使用 monitorenter 指令
    • 退出锁使用 monitorexit 指令

synchronized 普通同步方法

    public synchronized void m2() {System.out.println("----hello synchronized m2");}
  • 使用javap -v .\LockSyncDemo.class进行编译

image.png

  • 调用指令时检查方法的 ACC_SYNCHRONIZED 访问标志位是否被设置
    • 若被设置了,则线程会将先持有 monitor 锁,然后再执行方法
    • 最后在方法完成时(无论正常完成还是非正常完成)均会释放 monitor

synchronized 静态同步方法

public static synchronized void m3() {System.out.println("----hello static synchronized m3");
}
  • 执行反编译

image.png

  • ACC_STATIC, ACC_SYNCHRONIZED 访问标志位区分该方法是否为静态同步方法

对于 synchronized 的深入研究

面试题:为什么任何一个对象都可以成为一个锁

什么是管程 monitor

  • HotSport 虚拟机中,monitor 采用 ObjectMonitor 实现
  • C++源码执行过程

  • Object 底层实现基于 ObjectMonitor.cpp,所有类默认继承自 Object 类,因此每个对象天生就带着一个 monitor 对象
  • 每一个锁住的对象均会与 monitor 进行关联
  • objectMonitor.hpp 中的源码片段
// initialize the monitor, exception the semaphore, all other fields
// are simple integers or pointers
ObjectMonitor() {_header       = NULL;_count        = 0;			//记录该线程获取锁的次数_waiters      = 0,recursions   = 0;			//锁的重入次数_object       = NULL;_owner        = NULL;	    //指向持有ObjectMonitor对象的线程_WaitSet      = NULL;		//存放的处于wait状态的线程队列_WaitSetLock  = 0 ;			_Responsible  = NULL ;_succ         = NULL ;_cxq          = NULL ;FreeNext      = NULL ;_EntryList    = NULL ;		//存放处于等待锁block状态的线程队列_SpinFreq     = 0 ;_SpinClock    = 0 ;OwnerIsThread = 0 ;_previous_owner_tid = 0;
}

公平锁与非公平锁

公平锁是指多线程按照申请锁的顺序来获取锁,先来先得
Lock lock = new ReentrantLock(true); //true表示先来先得


非公平锁是指,获取锁的顺序不是按照申请锁的顺序
存在后申请的线程比先申请的线程优先获取锁
在高并发环境下,存在优先级翻转或者锁饥饿的状态
锁饥饿 : 某个线程长时间得不到锁
Lock lock = new ReentrantLock(false); //false 表示非公平锁,并发抢锁
Lock lock = new ReentrantLock(); //默认非公平锁

为什么会有公平锁和非公平锁的设计?

  • 恢复挂起的线程到真正锁的获取还是有时间差的
  • 对 CPU 而言时间差较为明细,非公平锁能够更充分利用 CPU 的时间片,减少 CPU 空闲时间

为什么默认使用非公平锁?

  • 使用多线程的一个考量点就是线程切换的开销
    • 采用非公平锁时,当一个线程请求锁获取同步状态,然后释放同步状态,则刚释放锁的线程在此时再次获得同步状态的概率就变得非常大,因此减少了线程的开销

什么时候用公平锁?什么时候用非公平锁?

  • 为了提高系统的吞吐量,提升性能,减少不必要的时间开销,应选择非公平锁
  • 公平锁需要结合具体场景进行讨论

可重入锁,又叫,递归锁

同一个线程在外层方法获取锁的时候,再进入该线程都内层方法会自动获取锁(前提,锁的对象是同一个),不会因为之前已经获取过还没释放而阻塞

  • Java 中 synchronized 和 ReentrantLock 都是可重入锁
  • 可重入锁可以一定程度上避免死锁
  • 可重入锁,即可多次进入同步域==>同步代码块,同步方法,lock()与 unlock()包裹的区域

隐式锁(synchronized 默认是可重入锁)

  • 在一个 synchronized 修饰的方法或代码块内部调用本类的其它 synchronized 修饰的方法或代码块时,永远可以得到锁

同步块

    private static void reEntryM1() {final Object object = new Object();new Thread(() -> {synchronized (object) {System.out.println(Thread.currentThread().getName() + "\t ----外层调用");synchronized (object) {System.out.println(Thread.currentThread().getName() + "\t ----中层调用");synchronized (object) {System.out.println(Thread.currentThread().getName() + "\t ----内层调用");}}}}, "t1").start();}
  • 在 main 方法中进行调用,测试结果

image.png

同步方法

     public synchronized void m1() {//指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。System.out.println(Thread.currentThread().getName()+"\t ----come in");m2();System.out.println(Thread.currentThread().getName()+"\t ----end m1");}public synchronized void m2(){System.out.println(Thread.currentThread().getName()+"\t ----come in");m3();}public synchronized void m3(){System.out.println(Thread.currentThread().getName()+"\t ----come in");}//main方法调用public static void main(String[] args){ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();new Thread(() -> {reEntryLockDemo.m1();}, "t1").start();}
  • 效果

image.png

synchronized 的可重入原理(基于 objectMonitor.hpp)

  • 每个锁对象都拥有一个锁计数器和一个指向持有锁的线程指针
    • 当执行 monitorenter 时,如果目标锁的计数器为零,那么没有被其他线程所持有,JVM 会将该锁对象持有的线程设置为当前线程,并将计数器加 1
    • 在目标锁对象的计数器不为零的情况下,如果锁的持有线程是但前线程,那么 JVM 可以将其计数器加 1,否则进入等待,直至持有线程释放该锁
    • 当执行 monitorexit 时,JVM 将锁对象的计数器减 1,计数器为 0 表示锁已被释放

显示锁(Lock,ReentrantLock)

    new Thread(() -> {lock.lock();try{System.out.println(Thread.currentThread().getName()+"\t ----come in外层调用");lock.lock();try{System.out.println(Thread.currentThread().getName()+"\t ----come in内层调用");}finally {lock.unlock();}}finally {// 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。lock.unlock();// 正常情况,加锁几次就要解锁几次}},"t1").start();new Thread(() -> {lock.lock();try{System.out.println(Thread.currentThread().getName()+"\t ----come in外层调用");}finally {lock.unlock();}},"t2").start();
  • 效果

image.png

  • lock()与 unlock()未一一匹对

image.png

  • 效果

image.png

  • t1 外层未释放锁,t2 陷入持续等待…

死锁以及排查

死锁是什么

参考往期文章

编写一个死锁 case

 public static void main(String[] args) {final Object objectA = new Object();final Object objectB = new Object();new Thread(() -> {synchronized (objectA) {System.out.println(Thread.currentThread().getName() + "\t 自己持有A锁,希望获得B锁");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (objectB) {System.out.println(Thread.currentThread().getName() + "\t 成功获得B锁");}}}, "A").start();new Thread(() -> {synchronized (objectB) {System.out.println(Thread.currentThread().getName() + "\t 自己持有B锁,希望获得A锁");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (objectA) {System.out.println(Thread.currentThread().getName() + "\t 成功获得A锁");}}}, "B").start();}

死锁的故障排查

  • jps -l
  • jstack pid

image.png
image.png

  • jconsole

image.png

小结


见后续

自旋锁 SpinLock

无锁->独占锁->读写锁->邮戳锁

无锁->偏向锁->轻量锁->重量锁


在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

排序(9.17)

1.排序的概念及其运用 1.1排序的概念 排序 :所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 稳定性 :假定在待排序的记录序列中,存在多个具有相同的关键字的记…

OSI参考模型和TCP/IP网络参考模型

1、OSI参考模型 1.1 产生背景 为了解决网络之间的兼容性问题,实现网络设备间的相互通讯,国际标准化组织ISO于1984年提出了OSIRM(Open System Interconnection Reference Model,开放系统互连参考模型)。OSI参考模型很快成为计算机网络通信的基础模型。由于种种原因,并没有…

Redis高可用三主三从集群部署

文章目录 🔊博主介绍🥤本文内容使用宝塔面板搭建集群规划配置验证 使用docker搭建使用脚本搭建📢文章总结📥博主目标 🔊博主介绍 🌟我是廖志伟,一名Java开发工程师、Java领域优质创作者、CSDN博…

聊一聊bpmn-js中的elementFactory模块

上一篇文章里我们了解了bpmn-js使用palette模块进行左侧小工具区域(也可以理解为调色板区域)的功能扩展,今天这个话题则是延续上期的palette进行开展的。 从上篇文章《聊一聊bpmn-js中的Palette》我们知道,PaletteProvider通过getPaletteEntries方法提供小工具Map对象,而单…

Python 实现 CR 指标计算 (带状能量线):股票技术分析的利器系列(13)

Python 实现 CR 指标计算 (带状能量线):股票技术分析的利器系列(13) 介绍算法公式 代码rolling函数介绍核心代码计算MID计算CR计算移动平均 完整代码 介绍 CR指标,又称带状能量线,通过计算股价的动能和变化…

MySQL的21个SQL经验

1. 写完SQL先explain查看执行计划(SQL性能优化) 日常开发写SQL的时候,尽量养成这个好习惯呀:写完SQL后,用explain分析一下,尤其注意走不走索引。 explain select userid,name,age from user where userid =10086 or age =18;2、操作delete或者update语句,加个limit(S…

BTC网络 vs ETH网络

设计理念 BTC 网络 比特币是一种数字货币,旨在作为一种去中心化的、不受政府或金融机构控制的电子货币。其主要目标是实现安全的价值传输和储存,比特币的设计强调去中心化和抗审查。 ETH 网络 以太坊是一个智能合约平台,旨在支持分散的应…

计网Lesson15 - TCP可靠传输

文章目录 1. 停止等待ARQ协议2. 连续ARQ协议与滑动窗口协议 1. 停止等待ARQ协议 ARQ(Automatic Repeat–reQuest)自动重传请求 几种重传情况 发送端丢失 发送方过久没有接收到接收方的确认报,这种情况会触发超时重传机制,发送方…

瑞_Redis_Redis客户端

文章目录 1 Redis客户端1.1 Redis命令行客户端1.2 图形化桌面客户端1.2.1 资源准备1.2.2 安装1.2.3 建立连接 🙊 前言:本文章为瑞_系列专栏之《Redis》的基础篇的Redis客户端章节。由于博主是从B站黑马程序员的《Redis》学习其相关知识,所以本…

适配器模式(Adapter Pattern) C++

上一节:原型模式(Prototype Pattern) C 文章目录 0.理论1.组件2.类型3.什么时候使用 1.实践1.基础接口和类2.类适配器实现3.对象适配器实现 0.理论 适配器模式(Adapter Pattern)是一种结构型设计模式,它允…

通过Power automate上传文件到SharePoint

上传文件到SharePoint 背景过程Apps开发准备Flow开发SharePoint数据库测试 背景 我们通常都可以通过一些form表单直接上传文件,但是有些时候我们需要对文件名字等信息做一些处理。这个时候我们就需要将文件的内容json流化,然后在流中还原回来。 过程 …

OpenHarmony分布式购物车案例展示~

简介 分布式购物车demo 模拟的是我们购物时参加满减活动,进行拼单的场景;实现两人拼单时,其他一人添加商品到购物车,另外一人购物车列表能同步更新,且在购物车列表页面结算时,某一人结算对方也能实时知道结…

STM32存储左右互搏 QSPI总线FATS文件读写FLASH W25QXX

STM32存储左右互搏 QSPI总线FATS文件读写FLASH W25QXX FLASH是常用的一种非易失存储单元,W25QXX系列Flash有不同容量的型号,如W25Q64的容量为64Mbit,也就是8MByte。这里介绍STM32CUBEIDE开发平台HAL库Quad SPI总线实现FATS文件操作W25Q各型号…

第12章-生成树协议

1. STP产生背景 1.1 桥接网络:网桥 1.2 交换机网络 1.3 解决方案 2. STP生成树协议 2.1 概念 2.2 BPDU(Bridge Protocol Data Unit) 2.3 选举机制(网桥:一进一出) 2.4 例题 2.5 端口状态 2.6 STP计时器 2.7 STP的问题 …

家装服务管理:Java技术的创新应用

✍✍计算机毕业编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java、…

C++ //练习 9.16 重写上一题的程序,比较一个list<int>中的元素和一个vector<int>中的元素。

C Primer(第5版) 练习 9.16 练习 9.16 重写上一题的程序,比较一个list中的元素和一个vector中的元素。 环境:Linux Ubuntu(云服务器) 工具:vim 代码块 /**********************************…

C++ //练习 9.14 编写程序,将一个list中的char*指针(指向C风格字符串)元素赋值给一个vector中的string。

C Primer(第5版) 练习 9.14 练习 9.14 编写程序,将一个list中的char*指针(指向C风格字符串)元素赋值给一个vector中的string。 环境:Linux Ubuntu(云服务器) 工具:vim…

基于 ResNet50和 SVM + 决策树的人脸口罩检测

欢迎收看,这篇文章是关于我如何使用 ResNet50作为特征提取器来构建掩码检测,然后使用支持向量机(SVM) 决策树和叠加集成方法作为分类器的一个快速解释。 为了向研究人员致敬,这个应用程序是基于研究论文,题目是“在2019冠状病毒…

智慧公厕:让城市更智慧、更环保

在现代社会,智慧公厕作为城市管理的重要一环,是智慧城市的重要组成部分,其建设的价值十出突出,是公共厕所信息化升级改造的核心方案。如智慧公厕源头厂家广州中期科技有限公司,所自主研发的智慧公厕整体解决方案&#…

最新Sora人工智能视频资源网址分享

1,了解什么是Sora * 什么是 OpenAI Sora? Sora 是由 OpenAI 开发的文本到视频模型。它可以按照用户的提示生成长达一分钟的高质量和一致的视频。 * 如何使用 OpenAI Sora 模型? 目前,OpenAI Sora 模型处于内测阶段,并将…