JVM 锁的种类

news2024/9/19 10:47:15

优质博文:IT-BLOG-CN

一、JVM 锁【偏向锁|轻量级锁|重量级锁】

对象头[每个对象都具有对象头] Mark:对象头的标记(32位),描述对象的hash、锁信息、垃圾回收标记、年龄;内容包括:①、指向锁记录的指针;②、指向monitor的指针;③、GC标记;④、偏向锁线程ID

偏向锁

偏向于第一个访问锁的线程,初次执行synchronized代码块时,通过CAS修改对象头里的标志位,锁对象变成了偏向锁。偏向锁无法使用自旋锁优化,因为一旦有其他线程申请锁,就破坏了偏向锁的假定。偏向锁的目标是,减少无竞争且只有一个线程使用锁的情况下,使用轻量级锁产生的性能消耗。轻量级锁每次申请、释放锁都至少需要一次CAS,但偏向锁只有初始化时需要一次CAS。如果明显存在其他线程申请锁,那么偏向锁将很快膨胀为轻量级锁。其特点如下:
【1】大部分情况下没有竞争的,所以可以通过偏向锁来提高性能;
【2】所谓偏向锁,就是偏心,即锁会偏向于当前已经占有的线程
【3】将对象Mark的标记设置为偏向,并将线程ID写入对象的Mark头中;
【4】只要没有竞争,获得偏向锁的线程,在将来进入代码块,不需要做同步;
【5】-XX:+UseBiasedLocking默认启动;
【6】在竞争激烈的场合,偏向锁会增加系统负担;

【代码示列】: 当没有锁竞争的时候,就会默认使用偏向锁。

public static List<Integer> numberList =new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
	long begin=System.currentTimeMillis();
	int count=0;
	int startnum=0;
	while(count<10000000){
		numberList.add(startnum);
		startnum+=2;
		count++;
	}
	long end=System.currentTimeMillis();
	System.out.println(end-begin);
}

开启偏向锁:-XX:+UseBiasedLocking -XX:+BiasedLockingStartupDelay=0
关闭偏向锁:-XX:-UseBiasedLocking -XX:-BiasedLockingStartupDelay=0

【结论】: 使用偏向锁,可以获得5%的性能提升。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的。关于偏向锁的撤销,需要等待全局安全点,即在某个时间点上没有字节码正在执行时,它会先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁,恢复到无锁(标志位为01)或轻量级锁(标志位为00)的状态。

轻量级锁

当前锁是偏向锁,此时有多个线程同时来竞争锁,偏向锁就会升级为轻量级锁。轻量级锁认为虽然竞争是存在的,但是理想情况下竞争的程度很低,通过自旋方式来获取锁,那么申请重量级锁都是浪费的。轻量级锁的目标是,减少无实际竞争情况下,使用重量级锁产生的性能消耗,包括系统调用引起的内核态与用户态切换线程阻塞造成的线程切换等。顾名思义,轻量级锁是相对于重量级锁而言的。使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。当然,由于轻量级锁天然瞄准不存在锁竞争的场景,如果存在锁竞争但不激烈,仍然可以用自旋锁优化,自旋失败后再膨胀为重量级锁。BasicObjectLock:嵌入在线程中

【1】普通锁处理性能不够理想,轻量级锁是一种快速的锁定方法。

【2】如果对象没有锁定:①、将对象头的Mark指针保存到锁对象中。②、将对象头设置为指向锁的指针(在线程空间中)

lock->set_displaced_header(mark);
 if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
	  TEVENT (slow_enter: release stacklock) ;
	  return ;
 }
//lock位于线程中 

【3】总结: ① 如果轻量级锁失败,表示存在竞争,升级为重量级锁(常规锁);② 在没有锁竞争的情况下,减少传统锁使用OS互斥产生的锁性能消耗问题;③ 在竞争激烈时,轻量级锁会做很多额外操作,导致性能下降;

自旋锁

自适应自旋解决的是“锁竞争时间不确定”的问题。JVM很难感知到确切的锁竞争时间,而交给用户分析就违反了JVM的设计初衷。自适应自旋假定不同线程持有同一个锁对象的时间基本相当,竞争程度趋于稳定,因此,可以根据上一次自旋的时间与结果调整下一次自旋的时间。然而,自适应自旋也没能彻底解决该问题,如果默认的自旋次数设置不合理(过高或过低),那么自适应的过程将很难收敛到合适的值:
【1】JDK1.7中,自旋锁为内置实现。
【2】在线程竞争存在时,如果线程可以很快获得锁,那么可以不再OS层挂起线程,让线程做几个空操作(自旋)
【3】如果同步块很长,自旋失败,会降低系统性能。
【4】如果同步块很短,自旋成功,节省线程挂起切换时间,提升系统性能。

偏向锁、轻量级锁、自旋锁总结:
【1】不是Java语言层面的锁优化方法;
【2】内置于JVM中的获取锁的优化方法和获取锁的步骤:① 偏向锁可用时,会尝试偏向锁;② 轻量级锁可用会尝试轻量级锁;③ 以上都失败,尝试自旋锁;④ 在失败尝试普通锁,使用OS互斥量在操作系统层面挂起;

更多链接

CAS(V,E,N)V:表示更新的变量,E:表示预期值,N:表示新值。当V=E时,才会将V的值设为N,如果 V值和 E值不同,则说明有其他线程做了更新,则当前线程什么都不做,最后 CAS 返回 V 的真实值。

在应用层面判断多线程的干扰,如果有干扰则通知线程重试。例如:java.util.concurrent.atomic.AtomicInteger

public final int getAndSet(int newValue) {
    for (;;) {
        int current = get();
        if (compareAndSet(current, newValue))
            return current;
    }
}

设置成功返回新值,设置失败返回旧值:public final boolean compareAndSet(int expect , int update)更新成功返回truejava.util.concurrent.atomic包使用无锁实现,性能高于一般的锁线程。此方法一般位于unsafa类中方法,保证了原子性。

重量级锁

如果线程的竞争很激励,线程的自旋超过了一定次数(默认循环10次,可以通过虚拟机参数更改),将轻量级锁升级为重量级锁(依然是 CAS 修改锁标志位,但不修改持有锁的线程ID),当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是忙等),等待将来被唤醒。

重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。简言之,就是所有的控制权都交给了操作系统,由操作系统来负责线程间的调度和线程的状态变更。而这样会出现频繁地对线程运行状态的切换,线程的挂起和唤醒,从而消耗大量的系统资。

二、合理使用锁机制

【1】减少锁持有时间(尽量使用synchronized块)

public synchronized void syncMethod(){
	othercode1();
	mutextMethod();
	othercode2();
}
public void syncMethod2(){
	othercode1();
	synchronized(this){
		mutextMethod();
	}
	othercode2();
}

【2】减少锁粒度: ① 将大对象拆成小对象,大大增加并行度,降低锁竞争;② 偏向锁,轻量级锁成功率高;③ ConcurrentHashMapHashMap的同步实现;④ Collections.synchronizedMap(map<key,value,m>;⑤ 返回SynchronizedMap对象。

public V get(Object key) {
	synchronized (mutex) {return m.get(key);}
}
public V put(K key, V value) {
	synchronized (mutex) {return m.put(key, value);}
}

ConcurrentHashMap JDK1.7时使用若干个Segment:Segment<key,vlaue>[] segmentsSegment(分段锁) 中维护 HashEntry<k,v>Put操作时,先定位到Segment,锁定一个Segment,执行put;在减少粒度后,ConcurrentHashMap允许若干个线程同时进入。

【3】锁分离: ① 根据功能进行锁分离;② ReadWriteLock;③ 读多写少的情况下,可以提高性能;

读锁写锁
读锁可访问不可访问
写锁不可访问不可访问
④ 读写分离思想可以延伸,只要操作互不影响,锁就可以分离;⑤ LinkedBlockingQueue链表阻塞队列

【4】锁粗化: 通常情况下,为保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。只有这样,等待在这个锁上的其他线程才能今早的获得资源执行任务。但是,凡是有一个临近值,如果对同一个锁不停的释放获取,其本身也会消耗宝贵的资源,反而不利于性能的优化。

for (int i=0; i < size; i++) {
    synchronized(lock){
        ......
    }
}

// 锁粗化后
synchronized(lock){
    for (int i=0; i < size; i++) {
        ......
    }
}

【5】锁消除: 在编译时,JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性,就会将这段代码所属的同步锁消除掉,从而到底提高程序性能的目的。锁消除的依据是逃逸分析的数据支持,如StringBufferappend()方法,或Vectoradd()方法,在很多情况下是可以进行锁消除的。

public static void main(String args[]) throws InterruptedException {
	long start = System.currentTimeMillis();
	for (int i = 0; i < CIRCLE; i++) {
		craeteStringBuffer("JVM", "Diagnosis");
	}
	long bufferCost = System.currentTimeMillis() - start;
	System.out.println("craeteStringBuffer: " + bufferCost + " ms");
}

public static String craeteStringBuffer(String s1, String s2) {
	StringBuffer sb = new StringBuffer();
	sb.append(s1);
	sb.append(s2);
	return sb.toString();
}

以上代码经过编译之后的字节码如下:

从上述结果可以看出,之前我们写的线程安全的加锁的StringBuffer对象,在生成字节码之后就被替换成了不加锁不安全的StringBuilder对象了,原因是StringBuffer的变量属于一个局部变量,并且不会从该方法中逃逸出去,所以我们可以使用锁消除(不加锁)来加速程序的运行。

【开启锁消除】:-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks ——执行时间198ms;
【关闭锁消除】:-server -XX:+DoEscapeAnalysis -XX:-EliminateLocks ——执行时间254ms;

三、公平锁/非公平锁

公平锁: 多个线程按照申请锁的顺序去获得锁,所有线程都在队列里排队,先来先获取的公平性原则。

优点: 所有的线程都能得到资源,不会饿死在队列中。

缺点: 吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,CPU唤醒下一个阻塞线程有系统开销。

非公平锁: 多个线程不按照申请锁的顺序去获得锁,而是同时以插队方式直接尝试获取锁,获取不到(插队失败),会进入队列等待(失败则乖乖排队),如果能获取到(插队成功),就直接获取到锁。

优点: 可以减少CPU唤醒线程的开销,整体的吞吐效率会高点

缺点: 可能导致队列中排队的线程一直获取不到锁或者长时间获取不到锁,活活饿死。

Java多线程并发操作,我们操作锁大多时候都是基于Sync本身去实现的,而Sync本身却是ReentrantLock的一个内部类,Sync继承AbstractQueuedSynchronizer

ReentrantLock默认是非公平锁,我们可以在构造函数中传入true,来创建公平锁。更多内容链接

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

四、可中断锁/不可中断锁

可中断锁: 指一个线程因为没有获得锁在阻塞等待过程中,可以中断自己阻塞的状态。不可中断锁: 恰恰相反,如果锁被其他线程获取后,当前线程只能阻塞等待。如果持有锁的线程一直不释放锁,那其他想获取锁的线程就会一直阻塞。

内置锁synchronized是不可中断锁,而ReentrantLock是可中断锁。

ReentrantLock获取锁定有三种方式:
【1】lock()如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于阻塞状态,直到该线程获取锁
【2】tryLock() 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false
【3】tryLock(long timeout,TimeUnit unit) 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false
【4】lockInterruptibly()如果获取了锁定立即返回;如果没有获取锁,线程处于阻塞状态,直到获取锁或者线程被别的线程中断

五、分段锁

分段锁其实是一种锁的设计,目的是细化锁的粒度,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7 中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

当需要put元素的时候,并不是对整个HashMap加锁,而是先通过hashcode知道要放在哪一个分段中,然后对这个分段加锁,所以当多线程put时,只要不是放在同一个分段中,可支持并行插入。

六、可重入锁

可重入锁,也叫做递归锁,是指在同一个线程在调外层方法获取锁的时候,再进入内层方法会自动获取锁。

对象锁或类锁内部有计数器,一个线程每获得一次锁,计数器 +1;解锁时,计数器 -1。

有多少次加锁,就要对应多少次解锁,加锁与解锁成对出现。

JAVA中的ReentrantLocksynchronized都是 可重入锁。可重入锁的一个好处是可一定程度避免死锁。

七、悲观锁/乐观锁

悲观锁

正如其名,它是指对数据修改时持保守态度,认为其他人也会修改数据。因此在操作数据时,会把数据锁住,直到操作完成。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。如果加锁的时间过长,其他用户长时间无法访问,影响程序的并发访问性,同时这样对数据库性能开销影响也很大,特别是长事务而言,这样的开销往往无法承受。

如果是单机系统,我们可以采用 JAVA 自带的 synchronized 关键字,通过添加到方法或同步块上,锁住资源 如果是分布式系统,我们可以借助数据库自身的锁机制来实现

select * from 表名 where id= #{id} for update

使用悲观锁的时候,我们要注意锁的级别,MySQL innodb 在加锁时,只有明确的指定主键或(索引字段)才会使用 行锁;否则,会执行 表锁,将整个表锁住,此时性能会很差。在使用悲观锁时,我们必须关闭MySQL数据库的自动提交属性,因为mysql默认使用自动提交模式。悲观锁适用于写多的场景,而且并发性能要求不高

乐观锁

乐观锁,从字面意思也能猜到个大概,在操作数据时非常乐观,认为别人不会同时修改数据,因此乐观锁不会上锁 只是在 提交更新 时,才会正式对数据的冲突与否进行检测。如果发现冲突了,则返回错误信息,让用户决定如何去做,fail-fast 机制 。否则,执行本次操作。

分为三个阶段:数据读取、写入校验、数据写入。

如果是单机系统,我们可以基于JAVACAS来实现,CAS是一种原子操作,借助硬件的比较并交换来实现。

如果是分布式系统,我们可以在数据库表中增加一个 版本号 字段,如:version

updateset ... , version = version +1 
where id= #{id} and version = #{version} 

操作前,先读取记录的版本号,更新时,通过SQL语句比较版本号是否一致。如果一致,则更新数据。否则会再次读取版本,重试上面的操作。

八、分布式锁

JAVA中的synchronizedReentrantLock等,都是解决单体应用单机部署的资源互斥问题。随着业务快速发展,当单体应用演化为分布式集群后,多线程、多进程分布在不同的机器上,原来的单机并发控制锁策略失效

此时我们需要引入分布式锁,解决跨机器的互斥机制来控制共享资源的访问。

分布式锁需要具备哪些条件:
【1】与单机系统一样的资源互斥功能,这是锁的基础
【2】高性能获取、释放锁
【3】高可用
【4】具备可重入性
【5】有锁失效机制,防止死锁
【6】非阻塞,不管是否获得锁,要能快速返回
实现方式多种多样,基于 数据库、Redis、以及Zookeeper等,这里讲下主流的基于Redis的实现方式:

加锁:

SET key unique_value  [EX seconds] [PX milliseconds] [NX|XX]

通过原子命令,如果执行成功返回 1,则表示加锁成功。注意:unique_value是客户端生成的唯一标识,区分来自不同客户端的锁操作 解锁要特别注意,先判断unique_value是不是加锁的客户端,是的话才允许解锁删除。毕竟我们不能删除其他客户端加的锁。

解锁: 解锁有两个命令操作,需要借助Lua脚本来保证原子性。

// 先比较 unique_value 是否相等,避免锁的误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

借助Redis的高性能,Redis实现分布式锁也是目前主流实现方式。但任何事情有利有弊,如果加锁的服务器宕机了,当slave节点还没来得及数据备份,那不是别的客户端也可以获得锁。

为了解决这个问题,Redis官方设计了一个分布式锁Redlock

基本思路: 让客户端与多个独立的Redis节点并行请求申请加锁,如果能在半数以上的节点成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败。

九、独享锁/共享锁

独享锁

独享锁,也有人叫它排他锁。无论读操作还是写操作,只能有一个线程获得锁,其他线程处于阻塞状态。

缺点:读操作并不会修改数据,而且大部分的系统都是 读多写少,如果读读之间互斥,大大降低系统的性能。下面的 共享锁 会解决这个问题。

JAVA中的ReentrantLocksynchronized都是独享锁

共享锁

共享锁是指允许多个线程同时持有锁,一般用在读锁上。读锁的共享锁可保证并发读是非常高效的。读写,写读 ,写写的则是互斥的。独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享

ReentrantReadWriteLock,其读锁是共享锁,其写锁是独享锁。

十、读锁/写锁

如果对某个资源是读操作,那多个线程之间并不会相互影响,可以通过添加读锁实现共享。如果有修改动作,为了保证数据的并发安全,此时只能有一个线程获得锁,我们称之为 写锁。读读是共享的;而 读写、写读 、写写 则是互斥的

JAVA中的ReentrantReadWriteLock就是一种 读写锁

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

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

相关文章

OpenShift 与 Rancher

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

Nacos2.4.1安装

Nacos官网| Nacos 配置中心 | Nacos 下载| Nacos 官方社区 | Nacos 官网 下载后解压 进入解压目录/bin window启动命令 startup.cmd -m standalone 用的JDK环境17 遇到报错 第一个办法 退回2.4.0哈哈哈 nacos配置为JDK1.8&#xff0c;而JDK17开始使用按模块引入包语法…

阀控多功能智能水表是什么?

阀控多功能智能水表是一种集成了多种先进技术和功能的新型水表&#xff0c;主要用于精确计量和控制水流量。这类水表不仅能够提供准确的数据记录&#xff0c;还具备远程控制、故障报警、数据分析等多种智能功能&#xff0c;适用于住宅、商业和工业等多种应用场景。本文将深入探…

泽众P-One性能测试平台中的环境管理与施压集群分配

P-One是泽众软件自主研发的一站式性能测试平台&#xff0c;在软件性能测试中发挥着重要作用。其中&#xff0c;环境管理和施压集群分配是P-One的关键功能&#xff0c;对于确保测试的准确性和有效性至关重要。 一、环境部署 P-One可以对已安装的压力机进行集中管理&#xff0c;…

240828-Gradio结合Html+Css+Javascript制作年历

A. 最终效果 需求描述 html javascript css 按年生成2016年至2116年的日历&#xff0c;要求如下&#xff1a; 二行六例&#xff0c;每个单元是一个月&#xff0c;且每个单元包含周次信息通过背景为红色的圆圈高亮显示当前的日期第一页显示今年&#xff0c;鼠标左边或键盘左键…

电梯按钮检测检测系统源码分享 # [一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示]

电梯按钮检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

RKNPU入门与实践 ---- 混合量化

目录 前言 一、混合量化 1.1 概念介绍 1.1.1 hybrid_quantization_step1 1.1.2 hybrid_quantization_step2 二、实际编写程序 2.1混合量化第一阶段 2.2 混合量化第二阶段 三、混合量化第一步接口参数proposal 前言 为什么要进行混合量化&#xff1f; 答案&#x…

Zookeeper未授权访问的漏洞处理

echo envi |nc 192.168.0.1 2181 这个命令可以用于获取Zookeepr&#xff08;下面有zk代替&#xff09;目标服务器的环境信息、部署路径、版本等敏感信息。如果这些信息被恶意利用&#xff0c;确实可能导致安全漏洞&#xff0c;进而对网络和服务器安全构成威胁。 1.执行zkCli.…

XSS LABS - Level 17 过关思路

关注这个靶场的其他相关笔记&#xff1a;XSS - LABS —— 靶场笔记合集-CSDN博客 0x01&#xff1a;过关流程 进入靶场&#xff0c;空空如也&#xff0c;右击页面&#xff0c;查看网页源码&#xff0c;找找可疑点&#xff1a; 可以看到&#xff0c;靶场默认传参&#xff0c;都传…

初赛试题-2022年CSP-J2

目录 先言 二、阅读程序&#xff08;判断题1.5分&#xff0c;选择题3分&#xff0c;共40分&#xff09; &#xff08;1&#xff09; 16. 17. 18. 19. 20. 21. &#xff08;2&#xff09; 22. 23. 24. 25. 26. 27. &#xff08;3&#xff09; 28. 29. 30. 3…

18.神经网络 - 非线性激活

神经网络 - 非线性激活 使用到的pytorch网站: Padding Layers&#xff08;对输入图像进行填充的各种方式&#xff09; 几乎用不到&#xff0c;nn.ZeroPad2d&#xff08;在输入tensor数据类型周围用0填充&#xff09; nn.ConstantPad2d&#xff08;用常数填充&#xff09; 在 …

【图像】灰度图与RGB图像的窗宽、窗位的值范围二三问

1. 16位灰度图的窗宽、窗位的值范围&#xff1f; 对于16位的灰度图像&#xff0c;每个像素点可以表示从0到2^16-1&#xff08;即0至65535&#xff09;之间的强度值。在医学影像领域&#xff0c;如CT扫描图像中&#xff0c;窗宽和窗位是用来调整图像对比度和亮度的参数&#xf…

【Spring Boot 3】【Web】自定义过滤器

【Spring Boot 3】【Web】自定义过滤器 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学习新技术总是要花费…

fabricjs 添加图片并实时更新小车位置

需求&#xff1a;添加小车图片到画布中&#xff0c; 后端通过ws实时更新小车位置 实际过程中遇到的问题&#xff1a; 设置中心点&#xff1a;我想把图片的中心设置为后端传来的坐标点&#xff0c;可以通过设置对象的originX和originY属性来改变对象的旋转和定位基点。让小车…

error:0308010C:digital envelope routines::unsupported【超详细图解】

目录 一、报错信息 二、分析原因 三、解决方案 一、报错信息 二、分析原因 node.js 18 不兼容oppsll&#xff0c;node.js v17以上版本中最近发布的OpenSSL3.0, 而OpenSSL3.0对允许算法和密钥大小增加了严格的限制 三、解决方案 方案1&#xff1a;打开终端&#xff0c;直接输入…

Zotero7最新(2024)安装、配置步骤

提醒&#xff1a; 绝大部分插件都已经适配了 Zotero 7&#xff0c;但是 ZotFile 插件已经停止对 Zotero 7 的支持&#xff0c;可以使用 Attanger 插件 替代。如果不适应还是建议装Zotero 6 一、安装 1、进入官网&#xff0c;点击下载进入下载界面 https://www.zotero.org/h…

OpenCV绘图函数(7)从一个椭圆定义中提取出多边形的顶点坐标函数ellipse2Poly()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 近似一个椭圆弧为一个多边形线。 函数 ellipse2Poly 计算近似指定椭圆弧的多边形线的顶点。它被 ellipse 函数所使用。如果 arcStart 大于 arcEn…

[羊城杯 2024] Crypto

文章目录 TH_Curvebaby_CurveRSA_lossTheoremPlus TH_Curve 题目描述&#xff1a; from Crypto.Util.number import * from secret import flagdef add_THcurve(P, Q):if P (0, 0):return Qif Q (0, 0):return Px1, y1 Px2, y2 Qx3 (x1 - y1 ** 2 * x2 * y2) * pow(a * …

使用统计方法在AMD GPU上使用JAX Profiler可靠地比较大型生成AI模型中的算法性能

Using statistical methods to reliably compare algorithm performance in large generative AI models with JAX Profiler on AMD GPUs — ROCm Blogs 摘要 本文提供了一份详细的指南&#xff0c;介绍如何在JAX实现的生成AI模型中测量和比较各种算法的性能。利用JAX Profiler…

Python编码系列—Docker容器的高效使用与实战应用

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…