并发锁机制之深入理解synchronized

并发锁机制之深入理解synchronized

Synchronized基础知识

i++的JVM字节码指令

getstatic i // 获取静态变量i的值 
iconst_1 // 将int常量1压入操作数栈
iadd // 自增 

i–的JVM字节码指令

getstatic i // 获取静态变量i的值 
iconst_1 // 将int常量1压入操作数栈
isub // 自减 

如果是单线程如上操作是顺序执行,没有问题

但多线程以上代码会交错执行

image-20240224091641423

临界区( Critical Section)

  • 一个程序运行多个线程本身是没有问题的

  • 问题在于多个线程访问共享资源

    • 多个线程共享资源其实也没有问题
    • 在多个线程对共享资源读写操作发生指令交错,就会出现问题

    一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区,其共享资源为临界资源

//临界资源
private static int counter = 0;public static void increment() { //临界区counter++;
}public static void decrement() {//临界区counter--;

竞态条件( Race Condition )

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

为了避免临界区的竞态条件发生,有多种手段可以达到目的

  • 阻塞式的解决方案: synchronized、Lock
  • 非阻塞式的解决方案:原子变量

注意:

虽然 java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:

互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码

同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点

synchronizzed的使用

synchronized同步块是Java提供的一种原子性内置锁,Java中的每个对象都可以把它当作一个同步锁来使用,这些Java内置的使用者看不到的锁称为内置锁,也叫做监视器锁。

加锁的方式

image-20240224092556016

解决共享问题

public static synchronized void increment() {counter++;
}public static synchronized void decrement() {counter--;
}
private static String lock = "";public static void increment() {synchronized (lock){counter++;}
}public static void decrement() {synchronized (lock) {counter--;}
}

synchronized 实际是用对象锁保证了临界区内代码的原子性

image-20240224092659044

synchronize高级篇

synchronized底层原理

synchronized是JVM内置锁,基于Monitor机制实现,依赖底层操作系统的互斥原语Mutex(互斥量),它是一个重量级锁,性能较低。当然,JVM内置锁在1.5之后版本做了重大的优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、自适应自旋(Adaptive Spinning)等技术来减少锁操作的开销,内置锁的并发性能已经基本与Lock持平。

Java虚拟机通过一个同步结果支持方法和方法中的指令序列的同步:monitor

同步方法是通过方法中access_flags中设置ACC_SYNCHRONIZED,同步代码块是通过monitorenter和monitorexit来实现。两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,对性能有较大影响。

查看synchronized的字节码指令序列

image-20240224094307617

image-20240224094318841

image-20240224094330521

Monitor(管程/监视器)

Monitor,直译为“监视器”,而操作系统领域一般翻译为“管程”。管程是指管理共享变量以及对共享变量操作的过程,让它们支持并发。在Java 1.5之前,Java语言提供的唯一并发语言就是管程,Java 1.5之后提供的SDK并发包也是以管程为基础的。除了Java之外,C/C++、C#等高级语言也都是支持管程的。synchronized关键字和wait()、notify()、notifyAll()这三个方法是Java中实现管程技术的组成部分。

MESA模型

在管程的发展史上,先后出现过三种不同的管程模型,分别是Hasen模型、Hoare模型和MESA模型。现在正在广泛使用的是MESA模型。下面我们便介绍MESA模型:

image-20240224094452026

管程中引入了条件变量的概念,而且每个条件变量都对应有一个等待队列。条件变量和等待队列的作用是解决线程之间的同步问题。

wait的使用姿势

对于MESA管程来说,有一个编程范式

while(条件不满足)wait();

唤醒的时间和获取锁继续执行的时间是不一致的,被唤醒的线程再次执行可能条件又不满足了,所以循环校验条件。MESA模型的wait()方法还有一个超时参数,为了避免线程进入等待队列永久阻塞

并发编程之 wait()为什么要处于while循环中?

notify()和notifyAll()分别何时使用

  • 所有等待线程拥有相同的等待条件
  • 所有等待线程被唤醒后,执行相同的操作
  • 只需要唤醒一个线程
Java语言的内置管程synchronized

Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模型进行了精简。MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量。模型如下图所示。

Monitor机制在Java中的实现

java.lang.Object 类定义了 wait(),notify(),notifyAll() 方法,这些方法的具体实现,依赖于 ObjectMonitor 实现,这是 JVM 内部基于 C++ 实现的一套机制。

ObjectMonitor其主要数据结构如下(hotspot源码ObjectMonitor.hpp):

ObjectMonitor() {_header       = NULL; //对象头  markOop_count        = 0;  _waiters      = 0,   _recursions   = 0;   // 锁的重入次数 _object       = NULL;  //存储锁对象_owner        = NULL;  // 标识拥有该monitor的线程(当前获取锁的线程) _WaitSet      = NULL;  // 等待线程(调用wait)组成的双向循环链表,_WaitSet是第一个节点_WaitSetLock  = 0 ;    _Responsible  = NULL ;_succ         = NULL ;_cxq          = NULL ; //多线程竞争锁会先存到这个单向链表中 (FILO栈结构)FreeNext      = NULL ;_EntryList    = NULL ; //存放在进入或重新进入时被阻塞(blocked)的线程 (也是存竞争锁失败的线程)_SpinFreq     = 0 ;_SpinClock    = 0 ;OwnerIsThread = 0 ;_previous_owner_tid = 0;

image-20240224095708013

在获取锁时,是将当前线程插入到cxq的头部,而释放锁时,默认策略(QMode=0)是:如果EntryList为空,则将cxq中的元素按原有顺序插入到EntryList,并唤醒第一个线程,也就是当EntryList为空时,是后来的线程先获取锁。_EntryList不为空,直接从_EntryList中唤醒线程。

对象的内存布局

Hotspot虚拟机中,对象在内存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和填充对齐(Padding)

  • 对象头:比如Hash码,对象所属的年代、对象锁、锁状态标志、偏向锁(线程)ID、偏向时间、数组长度(数组对象才有)等。
  • 实例数据:存放类的属性数据信息,包括父类的属性信息
  • 对齐填充:由于虚拟机要求对象的起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐

image-20240224100200602

对象头详解

Hotspot虚拟机的对象头包括

  • Mark Word
    • 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机中分别为32bit和64bit,官方称它为“Mark Word”。
  • Kclass Pointer
    • 对象头的另外一部分是klass类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 32位4字节,64位开启指针压缩或最大堆内存<32g时4字节,否则8字节。jdk1.8默认开启指针压缩后为4字节,当在JVM参数中关闭指针压缩(-XX:-UseCompressedOops)后,长度为8字节。
  • 数组长度
    • 如果对象是一个数组, 那在对象头中还必须有一块数据用于记录数组长度。 4字节

image-20240224100349300

使用JOL工具查看内存布局
<!-- 查看Java 对象布局、大小工具 -->
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.10</version>
public static void main(String[] args) throws InterruptedException {Object obj = new Object();//查看对象内部信息System.out.println(ClassLayout.parseInstance(obj).toPrintable());
  1. 使用job查看64位相同java对象(空对象),默认开启指针压缩,总大小显示16字节,前12字节位对象头

image-20240224100512857

  • OFFSET:偏移地址,单位字节
  • SIZE:占用的内存大小,单位为字节
  • TYPE DESCRIPTION:类型描述,其中object header为对象头
  • VALUE:对应内存中当前存储的值,二进制32位
  1. 关闭指针压缩后,对象头为16字节,-XX:UseCompressedOops

image-20240224100814493

Mark Word是如何记录锁状态的

Hotspot通过markOop类型实现Mark Word,具体实现位于markOop.hpp文件中。由于对象需要存储的运行时数据很多,考虑到虚拟机的内存使用,markOop被设计成一个非固定的数据结构,以便在极小的空间存储尽量多的数据,根据对象的状态复用自己的存储空间。

简单点理解就是:MarkWord 结构搞得这么复杂,是因为需要节省内存,让同一个内存区域在不同阶段有不同的用处。

Mark Word的结构

//  32 bits:
//  --------
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
//             size:32 ------------------------------------------>| (CMS free block)
//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
//  64 bits:
//  --------
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
//  size:64 ----------------------------------------------------->| (CMS free block)
//
//  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
//  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
//  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
//  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)。。。。。。
//    [JavaThread* | epoch | age | 1 | 01]       lock is biased toward given thread
//    [0           | epoch | age | 1 | 01]       lock is anonymously biased
//
//  - the two lock bits are used to describe three states: locked/unlocked and monitor.
//
//    [ptr             | 00]  locked             ptr points to real header on stack
//    [header      | 0 | 01]  unlocked           regular object header
//    [ptr             | 10]  monitor            inflated lock (header is wapped out)
//    [ptr             | 11]  marked             used by markSweep to mark an object
  • hash码:保存对象的hash码。运行期间调用System.identityHashCode()来计算,延迟计算,并把结果赋值到这里
  • age:保存分代年龄。表示对象被GC的次数,当该次数到达阈值的时候,对象就会被转移到老年代
  • biased_lock:偏向锁表示位,由于无锁和偏向锁的锁标识都是01,没办法进行区分,这里引入一位偏向锁标识位
  • lock:锁状态标识位。区分锁状态,比如11时表示对象待GC回收状态, 只有最后2位锁标识(11)有效。
  • JavaThread*:保存持有偏向锁的线程ID。偏向模式的时候,当某个线程持有对象的时候,对象这里就会被置为该线程的ID。 在后面的操作中,就无需再进行尝试获取锁的动作。这个线程ID并不是JVM分配的线程ID号,和Java Thread中的ID是两个概念。
  • epoch:保存偏向戳。偏向锁在CAS锁操作过程中,偏向性标识,表示对象更偏向哪个锁。

image-20240224101534369

  • ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。当锁获取是无竞争时,JVM使用原子操作而不是OS互斥,这种技术称为轻量级锁定。在轻量级锁定的情况下,JVM通过CAS操作在对象的Mark Word中设置指向锁记录的指针。
  • ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。如果两个不同的线程同时在同一个对象上竞争,则必须将轻量级锁定升级到Monitor以管理等待的线程。在重量级锁定的情况下,JVM在对象的ptr_to_heavyweight_monitor设置指向Monitor的指针

Mark Word中锁标记枚举

enum { locked_value             = 0,    //00 轻量级锁 unlocked_value           = 1,   //001 无锁monitor_value            = 2,   //10 监视器锁,也叫膨胀锁,也叫重量级锁marked_value             = 3,   //11 GC标记biased_lock_pattern      = 5    //101 偏向锁

image-20240224101615028

偏向锁

偏向锁是一种针对加锁操作的优化手段,经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了消除数据在无竞争情况下锁重入(CAS操作)的开销而引入偏向锁。对于没有锁竞争的场合,偏向锁有很好的优化效果。

/***StringBuffer内部同步***/
public synchronized int length() { return count; 
} 
//System.out.println 无意识的使用锁 
public void println(String x) { synchronized (this) {print(x); newLine(); } 

当JVM启用了偏向锁模式(jdk6默认开启),新创建对象的Mark Word中的Thread Id为0,说明此时处于可偏向但未偏向任何线程,也叫做匿名偏向状态(anonymously biased)。

偏向锁的延迟偏向

偏向锁模式存在偏向延迟机制:HotSpot虚拟机在启动后有个4s的延迟才会对每个新建的对象开启偏向锁模式。JVM启动时会进行一系列的复杂活动,比如装载配置,系统类初始化等等。在这个过程中会使用大量synchronized关键字对对象加锁,且这些锁大多数都不是偏向锁。为了减少初始化时间,JVM默认延时加载偏向锁。

//关闭延迟开启偏向锁
-XX:BiasedLockingStartupDelay=0
//禁止偏向锁
-XX:-UseBiasedLocking 
//启用偏向锁
@Slf4j
public class LockEscalationDemo{public static void main(String[] args) throws InterruptedException {log.debug(ClassLayout.parseInstance(new Object()).toPrintable());Thread.sleep(4000);log.debug(ClassLayout.parseInstance(new Object()).toPrintable());}

image-20240224102209846

偏向锁状态跟踪

public class LockEscalationDemo {public static void main(String[] args) throws InterruptedException {log.debug(ClassLayout.parseInstance(new Object()).toPrintable());//HotSpot 虚拟机在启动后有个 4s 的延迟才会对每个新建的对象开启偏向锁模式Thread.sleep(4000);Object obj = new Object();new Thread(new Runnable() {@Overridepublic void run() {log.debug(Thread.currentThread().getName()+"开始执行。。。\n"+ClassLayout.parseInstance(obj).toPrintable());synchronized (obj){log.debug(Thread.currentThread().getName()+"获取锁执行中。。。\n"+ClassLayout.parseInstance(obj).toPrintable());}log.debug(Thread.currentThread().getName()+"释放锁。。。\n"+ClassLayout.parseInstance(obj).toPrintable());}},"thread1").start();Thread.sleep(5000);log.debug(ClassLayout.parseInstance(obj).toPrintable());}

偏向锁撤销之调用对象HashCode

调用锁对象的obj.hashCode()或System.identityHashCode(obj)方法会导致该对象的偏向锁被撤销。因为对于一个对象,其HashCode只会生成一次并保存,偏向锁是没有地方保存hashcode的。

  • 轻量级锁会在锁记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 hashCode

当对象处于可偏向(也就是线程ID为0)和已偏向的状态下,调用HashCode计算将会使对象再也无法偏向:

  • 当对象可偏向时,MarkWord将变成未锁定状态,并只能升级成轻量锁;
  • 当对象正处于偏向锁时,调用HashCode将使偏向锁强制升级成重量锁。

偏向锁撤销之调用wait/notify

偏向锁状态执行obj.notify会升级成轻量级锁,调用obj.wait(timeout)会升级为重量级锁

synchronized (obj) {// 思考:偏向锁执行过程中,调用hashcode会发生什么?//obj.hashCode();//obj.notify();try {obj.wait(100);} catch (InterruptedException e) {e.printStackTrace();}log.debug(Thread.currentThread().getName() + "获取锁执行中。。。\n"+ ClassLayout.parseInstance(obj).toPrintable());

轻量级锁

  • 倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段,此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间多个线程访问同一把锁的场合,就会导致轻量级锁膨胀为重量级锁。

锁对象状态转换

image-20240224102751049

进阶:synchronized锁优化

从偏向锁的加锁过程中可以看出,当只有一个线程反复进行进入同步块时,偏向锁带来的性能开销基本可以忽略,但是当有其他线程尝试获取锁时,就需要等到safe ponit,再将偏向锁撤销为无锁状态或升级为轻量级,会消耗一定的性能,所以在多线程竞争频繁的情况下,偏向锁不仅不能提高性能,还会导致性能下降。于是,就有了批量重偏向和批量撤销的机制

原理

以class为单位,为每个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器+1,当这个值达到重偏向阈值(默认20)时,JVM就认为该class的偏向锁有问题,因此会进行批量重偏向。

每个class对象会有一个对应的epoch字段,每个处于偏向锁状态对象的Mark Word中也有该字段,其初始值为创建该对象时class中的epoch的值。每次发生批量重偏向时,就将该值+1,同时遍历JVM中所有线程的栈,找到该class所有正处于加锁状态的偏向锁,将其epoch字段改为新值。下次获得锁时,发现当前对象的epoch值和class的epoch不相等,那就算当前已经偏向了其他线程,也不会执行撤销操作,而是直接通过CAS操作将其Mark Word的Thread Id 改成当前线程Id。

当达到重偏向阈值(默认20)后,假设该class计数器继续增长,当其达到批量撤销的阈值后(默认40),JVM就认为该class的使用场景存在多线程竞争,会标记该class为不可偏向,之后,对于该class的锁,直接走轻量级锁的逻辑。

应用场景

批量重偏向机制是为了解决:一个线程创建了大量对象并执行了初始的同步操作,后来另一个线程也来将这对象作为锁对象进行操作,这样会导致大量的偏向锁撤销操作

批量撤销机制是为了解决:在明显多线程竞争剧烈的场景下使用偏向锁是不合适的。

JVM的默认参数值

设置JVM参数-XX:+PrintFlagsFinal,在项目启动时即可输出JVM的默认参数值

intx BiasedLockingBulkRebiasThreshold   = 20   //默认偏向锁批量重偏向阈值

我们可以通过-XX:BiasedLockingBulkRebiasThreshold 和 -XX:BiasedLockingBulkRevokeThreshold 来手动设置阈值

总结

  1. 批量重偏向和批量撤销是针对类的优化,和对象无关。
  2. 偏向锁重偏向一次之后不可再次重偏向。
  3. 当某个类已经触发批量撤销机制后,JVM会默认当前类产生了严重的问题,剥夺了该类的新实例对象使用偏向锁的权利

自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。

  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  • 在 Java 6 之后自旋是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,比较智能。
  • Java 7 之后不能控制是否开启自旋功能

注意:自旋的目的是为了减少线程挂起的次数,尽量避免直接挂起线程(挂起操作涉及系统调用,存在用户态和内核态切换,这才是重量级锁最大的开销)

锁粗化

假设一系列的连续操作都会对同一个对象反复加锁及解锁,甚至加锁操作是出现在循环体中的,即使没有出现线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。如果JVM检测到有一连串零碎的操作都是对同一对象的加锁,将会扩大加锁同步的范围(即锁粗化)到整个操作序列的外部。

StringBuffer buffer = new StringBuffer();
/*** 锁粗化*/
public void append(){buffer.append("aaa").append(" bbb").append(" ccc");

上述代码每次调用 buffer.append 方法都需要加锁和解锁,如果JVM检测到有一连串的对同一个对象加锁和解锁的操作,就会将其合并成一次范围更大的加锁和解锁操作,即在第一次append方法时进行加锁,最后一次append方法结束后进行解锁。

锁消除

锁消除即删除不必要的加锁操作。锁消除是Java虚拟机在JIT编译期间,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过锁消除,可以节省毫无意义的请求锁时间。

public class LockEliminationTest {/*** 锁消除* -XX:+EliminateLocks 开启锁消除(jdk8默认开启)* -XX:-EliminateLocks 关闭锁消除* @param str1* @param str2*/public void append(String str1, String str2) {StringBuffer stringBuffer = new StringBuffer();stringBuffer.append(str1).append(str2);}public static void main(String[] args) throws InterruptedException {LockEliminationTest demo = new LockEliminationTest();long start = System.currentTimeMillis();for (int i = 0; i < 100000000; i++) {demo.append("aaa", "bbb");}long end = System.currentTimeMillis();System.out.println("执行时间:" + (end - start) + " ms");}
}

StringBuffer的append是个同步方法,但是append方法中的 StringBuffer 属于一个局部变量,不可能从该方法中逃逸出去,因此其实这过程是线程安全的,可以将锁消除。

测试结果: 关闭锁消除执行时间4688 ms 开启锁消除执行时间:2601 ms

逃逸分析(Escape Analysis)

逃逸分析(Escape Analysis)

逃逸分析,是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。逃逸分析的基本行为就是分析对象动态作用域。

方法逃逸(对象逃出当前方法)

当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中。

线程逃逸((对象逃出当前线程)

这个对象甚至可能被其它线程访问到,例如赋值给类变量或可以在其它线程中访问的实例变量。

使用逃逸分析,编译器可以对代码做如下优化:

1.同步省略或锁消除(Synchronization Elimination)。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。

2.将堆分配转化为栈分配(Stack Allocation)。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。

3.分离对象或标量替换(Scalar Replacement)。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。

jdk6才开始引入该技术,jdk7开始默认开启逃逸分析。在Java代码运行时,可以通过JVM参数指定是否开启逃逸分析:

-XX:+DoEscapeAnalysis  //表示开启逃逸分析 (jdk1.8默认开启)
-XX:-DoEscapeAnalysis //表示关闭逃逸分析。
-XX:+EliminateAllocations   //开启标量替换(默认打开)

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

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

相关文章

Uipath 读取Word模板实现录用通知书PDF批量生成

本文主要讲解如何使用Uipath 读取Excel 面试人员信息表&#xff0c;读取Word模板&#xff0c;再批量生成录用通知书PDF文件&#xff0c;该自动化大大提高了HR 的工作效率。 注&#xff1a;本方案实现采用无代码模式&#xff0c;通过拖拉控件实现。 1. 数据准备 1.1 面试人员…

Rust核心:【所有权】相关知识点

rust在内存资源管理上采用了&#xff08;先进优秀&#xff1f;算吗&#xff09;但特立独行的设计思路&#xff1a;所有权。这是rust的核心&#xff0c;贯穿在整个rust语言的方方面面&#xff0c;并以此为基点来重新思考和重构软件开发体系。 涉及到的概念点&#xff1a;借用&am…

计网网络层

文章目录&#xff1a; 文章目录 概述服务面向连接的虚电路服务无连接的数据报服务 IPv4分类编址A类地址B类地址C类地址 划分子网默认子网掩码 无分类编址路由聚合 应用规划 IP数据报的发送和转发过程静态路由配置路由选择协议静态路由选择动态路由选择路由信息协议RIP工作过程更…

Python中if __name__ == ‘__main__‘:的原理、作用和实践

Python中if name ‘main‘&#xff1a;的原理、作用和实践 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程 &#x1f448; 希望得到您的…

Linux---权限管理(ACL权限、特殊位和隐藏属性)

目录 1.ACT权限 1.1什么是ACT权限 1.2ACT图解 2.操作步骤 2.1添加测试目录、用户、组&#xff0c;并将用户添加到组 2.2修改目录的所有者和所属组 2.3设定权限 2.4为临时用户分配权限 2.4.1添加临时用户 2.4.2为临时用户分配特定权限 2.4.3查看目录权限&#xff0c;注…

PostgreSQL如何使用UUID

离线安装时&#xff0c;一般有四个包&#xff0c;都安装的话&#xff0c;只需要开启uuid的使用即可&#xff0c;如果工具包(即 postgresql11-contrib&#xff09;没有安装的话&#xff0c;需要单独安装一次&#xff0c;再进行开启。 开启UUID方法 下面介绍一下如何开启&#…

Unity实现帧序列

一、目的 1.想实现序列帧效果 自己使用Animation一直无法实现动画播放效果 二、参考 1. Unity序列帧动画——Sprite图片集制作UI动画_unity 序列帧动画图集-CSDN博客 结果&#xff1a;很好用&#xff0c;能实现效果 三、实操 新建Image&#xff0c;增加Animator组件&#x…

小保司的理赔是否有保障?

《小保司的理赔是否有保障&#xff1f;》 预计6-7分钟读完 连续日更&#xff1a;第7天 作者&#xff1a;罗师兄 微信号&#xff1a;luoyun515 同一个人&#xff0c;同样的重疾险责任&#xff0c; 同样的保额&#xff0c;同样的缴费方式&#xff0c; 不同的保司保费可以相…

LLMs之Gemma:Gemma(Google开发的新一代领先的开源模型)的简介、安装、使用方法之详细攻略

LLMs之Gemma&#xff1a;Gemma(Google开发的新一代领先的开源模型)的简介、安装、使用方法之详细攻略 导读&#xff1a;此文章介绍了Google推出的新一代开源模型Gemma&#xff0c;旨在帮助研发人员负责任地开发AI。 背景&#xff1a; >> Google长期致力于为开发者和研究人…

Sora:AI视频模型的革新者

随着人工智能技术的飞速发展&#xff0c;AI视频模型已成为科技领域的新热点。在这个浪潮中&#xff0c;OpenAI推出的首个AI视频模型Sora&#xff0c;以其卓越的性能和前瞻性的技术&#xff0c;引领着AI视频领域的创新发展。本文将从Sora的技术特点、应用场景以及对未来创作方式…

深入探究node搭建socket服务器

自从上篇中sokect实现了视频通话&#xff0c;但是是使用ws依赖库实现的服务端&#xff0c;所以最近再看ws源码&#xff0c;不看不知道&#xff0c;一看很惊讶。 接下来一点点记录一下&#xff0c;如何搭建一个简易的服务端socket&#xff0c;来实现上次的视频通讯。 搭建一个…

SwiftUI 支持拖放功能的集合视图(Grid)如何捕获手指按下并抬起这一操作

功能需求 假设我们开发了一款 SwiftUI 应用,其中用户可以通过拖放 Grid 中的 Cell 来完成一些操作。现在,我们希望用户在某个 Cell 被按下并随后抬起手指时得到通知,这能够实现吗? 如上图所示,我们准确地捕获到了手指在 Grid 的 Cell 上按下再抬起这一操作!那么它是如何…

PLC_博图系列☞基本指令“赋值”

PLC_博图系列☞基本指令“赋值” 文章目录 PLC_博图系列☞基本指令“赋值”背景介绍&#xff1a;赋值说明参数示例 关键字&#xff1a; PLC、 西门子、 博图、 Siemens 、 赋值 背景介绍 这是一篇关于PLC编程的文章&#xff0c;特别是关于西门子的博图软件。我并不是专业的…

Jetson Xavier NX 与笔记本网线连接 ,网络共享,ssh连接到vscode

Jetson Xavier NX 与笔记本网线连接 &#xff0c;网络共享&#xff0c;ssh连接到vscode Jetson Xavier NX桌面版需要连接显示屏、鼠标和键盘&#xff0c;操作起来并不方便&#xff0c;因此常常需要ssh远程连接到本地笔记本电脑&#xff0c;这里介绍一种连接方式&#xff0c;通过…

【区块链】联盟链

区块链中的联盟链 写在最前面**FAQs** 联盟链&#xff1a;区块链技术的新兴力量**联盟链的定义****联盟链的技术架构**共识机制智能合约加密技术身份认证 **联盟链的特点**高效性安全性可控性隐私保护 **联盟链的应用场景****金融服务****供应链管理****身份验证****跨境支付**…

Vue2页面转化为Vue3

vue2element-ui转化为Vue3element plus 后台管理系统&#xff1a;增删查改 vue2页面&#xff1a; <template><div class"app-container"><div><el-form:model"queryParams"ref"queryForm"size"small":inline&qu…

高级语言期末2012级A卷

1.编写函数&#xff0c;输出任意正整数n的位数&#xff08;n默认为存储十进制的整形变量&#xff09; 例如&#xff1a;正整数13&#xff0c;则输出2,&#xff1b;正整数3088&#xff0c;则输出4 #include <stdio.h>int func(int n) {int count0;while(n>0) {n/10;co…

Stable Diffusion 3 发布,AI生图效果,再次到达全新里程碑!

AI生图效果&#xff0c;再次到达全新里程碑&#xff01; Prompt&#xff1a;Epic anime artwork of a wizard atop a mountain at night casting a cosmic spell into the dark sky that says "Stable Diffusion 3" made out of colorful energy 提示&#xff08;意译…

mybatis常用标签

一.定义sql语句 1.select 标签 属性介绍: &#xff08;1&#xff09;id :唯一的标识符. &#xff08;2&#xff09;parameterType:传给此语句的参数的全路径名或别名 例:com.test.poso.User或user &#xff08;3&#xff09;resultType :语句返回值类型或别名。注意&#xff…

一周学会Django5 Python Web开发-Http请求HttpRequest请求类

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计25条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…