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

synchronized实现原理及优化

一、概述

线程安全在并发编程中是重要关注点,造成线程安全问题的主要诱因有两个:一是存在共享数据(也称临界资源),二是存在多个线程共同操作共享数据。synchronized关键字能够保证在同一时刻只有一个线程可以执行某个方法或某个代码块。

1、synchronized作用

原子性:synchronized保证语句块内操作是原子的

可见性:synchronized保证可见性(通过“在执行unlock之前,必须先把此变量同步回主内存”实现)

有序性:synchronized保证有序性(通过“一个变量在同一时刻只允许一条线程对其进行lock操作”)

2、synchronized使用

修饰实例方法:作用于当前实例加锁,进入同步代码前要获得当前实例的锁
修饰静态方法:作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

二、实现原理

synchronized在Java中实现原理主要是基于Java的内存模型(JMM)和对象锁机制。

1、Java内存模型(JMM)

依赖于JMM对一个变量unlock之前必须要同步到主内存,对变量进行lock操作将会清空本地内存中此变量的值,从新从主内存中load或assign初始化变量值。JMM定义了线程与主内存之间的抽象关系

1. 线程之间的共享变量存储在主内存中。

2. 每个线程都有一个私有的本地内存(也称为工作内存),本地内存中存储了该线程使用到的主内存副本的拷贝

3. 线程对变量的所有操作(读取、赋值等)都必须在自己的本地内存中进行,而不能直接读写主内存中的变量

多线程在JMM内存模型中运行机制

2、对象锁机制

synchronized通过对象锁机制来保证同一时刻只有一个线程可以访问被锁定的代码块或方法。在JVM中synchronized的实现都是基于进入和退出Monitor对象来实现方法同步和代码块同步的。所以谈synchronized的实现需要先说下对象在JVM中的存储:Java对象头、Monitor对象监听器。

2.1 对象结构

在HotSpot虚拟机中,对象在堆内存存储的布局主要分为三个区域:对象头、实例数据、填充数据。

实例数据:存放类的属性数据信息,包括父类的属性信息。若是数组的实例部分还包括数组的长度。这部分内存按4字节对齐。

填充数据:由于虚拟机要求对象的起始地址必须是8字节的整数倍。填充数据不是必须存在的。

对象头:对象头包括了分为两部分信息,第一部分存储对象自身运行时数据,如哈希码、GC分代年龄、锁状态等信息被称为MarkWord。另一部分用于存储执行对象类型数据的指针被称为ClassPoint。若是数组对象还会有一个额外部分存储数组长度。

2.2 对象头组成

对象头包括了MarkWord和ClassPoint两部分,若是数据对象还有一个额外部分存储数组长度。

MarkWord

MarkWord主要用来存储对象自身运行时的数据信息,如hashCode、GC分代年龄等。MarkWord的位长度为JVM的一个Word大小,32位JVM的MarkWord是32位,64位JVM的MarkWord是64位。

其中各部的含义如下:

锁标志位(lock):用低两位表示锁标志位,该标记位的数据不同代表MarkWord的含义不同。

偏向锁位(1bit)锁标记位(2bit)锁状态
001无锁状态
101偏向锁
000轻量级锁
010重量级锁
011GC标记信息

偏向锁位(bias_lock):对象是否启动偏向锁标记,只占1个二进制位。为1时表示对象启动偏向锁,为0时表示对象没有偏向锁。

分代年龄(age):4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。

hashCode:31位的对象标识Hash码,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程Monitor中。

JavaThread:持有偏向锁的线程ID。

Epoch:偏向时间戳。

lock record(ptr_to_lock_record):指向栈中锁记录的指针。

互斥量(ptr_to_heavyweight_monitor):指向monitor对象(也称为管程或监视器锁)的起始地址,每个对象都存在着一个monitor与之关联,对象与其monitor之间的关系有存在多种实现方式,如monitor对象可以与对象一起创建销毁或当前线程试图获取对象锁时自动生,但当一个monitor被某个线程持有后,它便处于锁定状态。

ClassPoint

这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。

如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩,其中,oop即ordinary object pointer普通对象指针。开启该选项后,下列指针将压缩至32位:

  • 每个Class的属性指针(即静态变量)
  • 每个对象的属性指针(即对象变量)
  • 普通对象数组的每个元素指针

当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等

2.3 Monitor监视器锁

Monitor介绍

在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)

ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象),_owner指向持有ObjectMonitor对象的线程。

当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSe t集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。执行过程如下:

由此看来,monitor对象存在于每个Java对象的对象头中(存储的指针的指向),synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因,同时也是notify/notifyAll/wait等方法存在于顶级对象Object中的原因。

对象Object的Monitor机制

Java虚拟机给每个对象和class字节码都设置了一个监听器Monitor,用于检测并发代码的重入,同时在Object类中还提供了notify和wait方法来对线程进行控制。

Object类中的代码如下:

public class Object {
…
private transient int shadow$monitor;
public final native void notify();
public final native void notifyAll();
public final native void wait() throws InterruptedException;
public final void wait(long millis) throws InterruptedException {
wait(millis, 0);
}
public final native void wait(long millis, int nanos) throws InterruptedException;
…
}

Monitor可以类比为一个特殊的房间,这个房间中有一些被保护的数据,Monitor保证每次只能有一个线程能进入这个房间进行访问被保护的数据,进入房间即为持有Monitor,退出房间即为释放Monitor。

当一个线程需获取锁对象的Monitor时,它会首先在entryList入口队列中排队,若无线程正在持有锁对象的Monitor,那么该线程会和entryList队列、waitSet队列中其他线程进行竞争(即通过CPU调度),选出一个线程来获取锁对象的Monitor,执行受保护的方法或代码块。执行完毕后释放锁对象的Monitor。若已经有线程持有锁对象的Monitor,那么需要等待其释放Monitor后再进行竞争。

waitSet队列:当一个线程拥有Monitor后,经过某些条件的判断(比如用户取钱发现账户没钱),这个时候需要调用Object的wait方法,线程就释放了Monitor,进入waitSet队列,等待Object的notify方法(比如用户向账户里面存钱)。当该对象调用了notify方法或者notifyAll方法后,waitSet中的线程就会被唤醒,然后在waitSet队列中被唤醒的线程和entryList队列中的线程一起通过CPU调度来竞争对象的Monitor,最终只有一个线程能获取对象的Monitor。

三、JDK1.6对synchronized的优化

synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高。这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。JDK团队在JDK1.6中对synchronized进行了大量优化,主要包括锁消除、锁膨胀、锁升级三部分。

1、锁消除

当JVM在JIT(Just-In-Time)编译时,在Synchronized修饰的代码中的锁不可能被其他线程访问到即不存在操作临界资源竞争情况,那么JVM在编译时会将这个锁清除。这样可以避免无谓的锁操作,提升执行效率。即便写了Synchronized也不会触发。

2、锁膨胀

在JDK1.6中引入了锁膨胀的优化技术。当JVM检测到一段代码中有多次加锁和解锁的操作时,若这些锁针对同一个对象,并且加锁和解锁操作频繁但加锁范围小,JVM会将锁的范围扩展到更大区域,从而减少锁的获取和释放次数,降低锁的开销。

如在for循环中频繁的获取和释放锁资源,这样带来的消耗很大,通过锁膨胀将锁范围扩大,减少资源消耗。代码如下:

		public void method(){for(int i = 0; i< 999999; i++){synchronized(对象){}}}

JVM对上面代码编译时会触达锁膨胀,编译后代码

		public void method(){synchronized(对象){for(int i = 0; i< 999999; i++){}}	}

3、锁升级

ReentrantLock的实现是先基于乐观锁的CAS尝试获取锁资源,若拿不到资源才会挂起线程。在JDK1.6中synchronized引入了无锁偏向锁、轻量级锁和重量级锁等多种锁状态。锁的升级是

为了提高获取锁和释放锁的效率,但升级后的锁会带来更好的性能开销。锁升级是单向的,从偏量锁开始根据竞争程度逐步升级为轻量级锁和重量级锁。

3.1 无锁

当一个线程访问一个没有被锁定的代码块时,这个代码块处于无锁状态。此时,任何线程都可以访问这个代码块,不需要进行任何锁的竞争。

3.2 偏向锁

偏向锁的目的是共享数据在无竞争的情况下提高获取锁的性能。

获取偏向锁

1. 判断锁对象的锁状态否为“101”-可偏向状态
2. 若为可偏向状态,则判断锁对象头中的线程ID是否是当前线程,若是进入同步块;
3. 若线程ID并未指向当前线程,会基于CAS方式尝试将偏向锁指向当前线程。若操作成功,将Mark Word中线程ID更新为当前线程ID,进入同步块
4. 若操作失败,代表出现了锁的竞争,准备撤销偏向锁。会根据线程是否处于活动状态,决定是转换为无锁状态还是升级为轻量级锁。

当一个线程第一次访问一个被synchronized锁保护的代码块时,这个代码块会尝试进入“101”-偏向锁状态。此时,JVM会将锁对象头中的标记位设为“偏向模式”,并记录持有锁的线程ID。此后,该线程再次访问这个代码块时,可以直接获取锁,不需要进行任何锁的竞争。偏向锁可以提高无竞争情况下的性能。

释放偏向锁

偏向锁使用了遇到竞争才释放锁的机制。偏向锁的撤销需要等待全局安全点,然后它会首先暂停拥有偏向锁的线程,然后判断线程是否还活着,如果线程还活着,则升级为轻量级锁,否则,将锁设置为无锁状态。

3.3 轻量级锁

轻量级锁也是JDK1.6引入的新型锁机制。它不是用来替换重量级锁的,它的本意是在没有多线程竞争的情况下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

轻量级锁是一种基于CAS自适应自旋锁的方式尝试获取锁资源。使用CAS尝试将线程ID存储到锁对象的对象头中。若操作成功,则表示当前线程成功获取锁。若操作失败,则表示锁已被其他线程持有,当前线程需要等待或其他同步操作。

获取轻量级锁

1. 当线程尝试进入同步代码或方式时,JVM会检查锁对象的状态。JVM首先在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前Mark Word的拷贝。

2. 若锁对象的Mark Word表明锁处于无锁状态(即未被任何线程持有),JVM会尝试将锁膨胀为轻量级锁。膨胀过程中JVM会将锁对象的Mark Word更新为指向当前线程的栈帧中的锁记录(Lock Record)的指针。

3. 为确保轻量级锁获取是原子的,JVM会利用CAS自适应自旋锁方式尝试将锁对象的Mark Word更新为指向当前线程的锁记录。

4. 若更新成功,那么该线程就拥有了该对象的锁,并且锁对象的Mark Word锁标志位转变为“00”,即表示此对象处于轻量级锁定状态;

5. 若更新失败,JVM首先会检查锁对象的Mark Word是否指向当前线程的栈帧,若锁对象被其他线程持有,则当前线程会自旋等待(在一定时间被循环尝试重新获取锁)。

6. 若自旋次数超过预设的阈值,或JVM检测到有多个(两个以上)线程在竞争同一个锁,JVM会将锁升级为重量级锁。

自适应自旋锁:是基于CAS操作实现,线程如果自旋成功了,那么下次自旋的次数会更加多;反之会减少自旋次数甚至取消。

释放轻量级锁

如果锁对象的Mark Word仍然指向线程的锁记录,那就用CAS操作将锁对象当前的Mark Word与线程栈帧中的Displaced Mark Word交换回来,如果替换成功,整个同步过程就完成了。如果替换失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。

3.4 重量级锁

Synchronized的重量级锁是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。

3.5 偏向锁、轻量级锁和重量级锁对比

1. 如果是单线程使用,偏向锁的代价最小,每次判断当前线程是否独占该锁即可

2. 如果出现了其他线程竞争,则偏向锁就会升级为轻量级锁

3. 线程达到最大自旋次数,升级为重量级锁

四、与ReentrantLock区别

1、底层实现

synchronized:是JVM层面的锁,通过对象头中的Mark Word来实现锁的获取和释放。底层是基于ObjectMonitor实现。它支持偏向锁、轻量级锁和重量级锁等多种锁状态,以优化性能。

ReentrantLock:是API层面的锁,基于AbstractQueuedSynchronizer(AQS)框架实现。AQS是一个用于构建锁和其他同步类的框架,提供了丰富的同步原语。

2、锁的获取和释放

synchronized:隐式地获取和释放锁。当线程进入synchronized修饰的方法或代码块时,会自动获取锁,并在方法或代码块执行完毕后自动释放锁。

ReentrantLock:显式地获取和释放锁。线程需要调用lock()方法来获取锁,并在使用完毕后调用unlock()方法来释放锁,也可以通过tryLock()方法尝试获取锁。这种显式的方式提供了更高的灵活性,但也要求程序员必须确保在finally块中释放锁,以避免死锁。

3、锁的公平性

synchronized:非公平锁。synchronized无法保证等待时间最长的线程会最先获得锁。

ReentrantLock:可以是公平锁,也可以是非公平锁。通过构造函数中的布尔值参数可以指定锁的公平性。公平锁会按照线程请求锁的顺序来分配锁,而非公平锁则允许插队。

4、锁的灵活性

synchronized:是Java语言的关键字,使用起来相对简单,但灵活性较低。它无法设置超时时间,也无法尝试获取锁而不阻塞当前线程。

ReentrantLock:提供了更高的灵活性。可以设置超时时间或tryLock()方法,尝试获取锁而不阻塞当前线程,以及检查锁是否被某个线程持有等。

5、响应中断

synchronized:无法响应中断。当线程在synchronized代码块中等待锁时,如果线程被中断,它会继续等待直到获取到锁,然后才能抛出InterruptedException。

ReentrantLock:可以响应中断。当线程在ReentrantLock的lock()或lockInterruptibly()方法中等待锁时,如果线程被中断,它会立即抛出InterruptedException,并释放已经占有的锁(如果是lockInterruptibly())。

6、锁的绑定条件

synchronized:不支持绑定多个条件。synchronized只能用于控制对共享资源的访问,而无法提供复杂的线程间通信机制。

ReentrantLock:支持绑定多个Condition条件。ReentrantLock可以与多个Condition对象关联,以实现更复杂的线程间通信和协作。

相关文章:

synchronized实现原理及优化

一、概述 线程安全在并发编程中是重要关注点&#xff0c;造成线程安全问题的主要诱因有两个&#xff1a;一是存在共享数据&#xff08;也称临界资源&#xff09;&#xff0c;二是存在多个线程共同操作共享数据。synchronized关键字能够保证在同一时刻只有一个线程可以执行某个…...

NLP 之词的表示与语言模型

表示的基本原理&#xff1a; 机器无法理解文字&#xff0c;却能进行复杂的数学运算——神经网络只要够深、够复杂&#xff0c;就能拟合足够复杂的数学模式。把文字嵌入&#xff08;embed&#xff09;到一个向量空间中去。 词表示&#xff08;Word Representation&#xff09;…...

每天一个数据分析题(四百七十一)- 假设检验

下列对假设检验的描述合理的是? A. 备择假设是研究者想收集证据予以支持的假设 B. 原假设是研究者想收集证据予以推翻的假设 C. 原假设是研究者想收集证据予以支持的假设 D. 备择假设是研究者想收集证据予以推翻的假设 数据分析认证考试介绍&#xff1a;点击进入 题目来…...

《系统架构设计师教程(第2版)》第13章-层次式架构设计理论与实践-04-数据访问层设计

文章目录 1. 五种数据访问模式1.1 在线访问1.2 DAO1.3 DTO1.4 离线数据模式1.5 对象/关系映射 (O/R Mapping) 2. 工厂方法模式在数据访问层应用3 ORM、Hibernate与CMP2.0设计思想3.1 ORM3.2 Hibernate1&#xff09;概述2&#xff09; Hibernate的架构&#xff08;2023年的考题&…...

【视觉SLAM】 十四讲ch7习题

简介 本文主要内容是《视觉SLAM十四讲》&#xff08;第二版&#xff09;第7章的习题解答&#xff0c;并介绍了在解答习题中的一下思考和总结的经验。本文代码部分参考了&#xff1a;HW-of-SLAMBOOK2 1、除了本书介绍的ORB特征点&#xff0c;你还能找到哪些特征点&#xff1f;…...

K-近邻算法(二)

三、 kd 树 问题导⼊&#xff1a; 实现k 近邻算法时&#xff0c; 主要考虑的问题是如何对训练数据进⾏快速 k 近邻搜索。这在特征空间的维数⼤及训练数据容量⼤时尤其必要。 k 近邻法最简单的实现是线性扫描&#xff08;穷举搜索&#xff09;&#xff0c;即要计算输⼊实例与…...

WPF学习(2)-UniformGrid控件(均分布局)+StackPanel控件(栈式布局)

UniformGrid控件&#xff08;均分布局&#xff09; UniformGrid和Grid有些相似&#xff0c;只不过UniformGrid的每个单元格面积都是相等的&#xff0c;不管是横向的单元格&#xff0c;或是纵向的单元格&#xff0c;它们会平分整个UniformGrid。 UniformGrid控件提供了3个属性…...

ANTSDR E310

ANTSDR E310是一款由微相科技有限公司&#xff08;MicroPhase&#xff09;推出的软件无线电&#xff08;SDR&#xff09;平台&#xff0c;专为现场部署设计。以下是对ANTSDR E310的详细介绍&#xff1a; 一、主要特点 独立运行的软件无线电&#xff1a;ANTSDR E310具备独立运…...

MySQL 5.7 DDL 与 GH-OST 对比分析

作者&#xff1a;来自 vivo 互联网存储研发团队- Xia Qianyong 本文首先介绍MySQL 5.7 DDL以及GH-OST的原理&#xff0c;然后从效率、空间占用、锁阻塞、binlog日志产生量、主备延时等方面&#xff0c;对比GH-OST和MySQL5.7 DDL的差异。 一、背景介绍 在 MySQL 数据库中&…...

【Python】爬取网易新闻今日热点列表数据并导出

1. 需求 从网易新闻的科技模块爬取今日热点的列表数据&#xff0c;其中包括标题、图片、标签、发表时间、路径、详细文本内容&#xff0c;最后导出这些列表数据到Excel中。 网易科技新闻网址&#xff1a;https://tech.163.com 2. 解决步骤 2.1 前期准备 爬虫脚本中需要引用…...

软件设计之HTML5

软件设计之HTML5 【狂神说Java】HTML5完整教学通俗易懂 学习内容&#xff1a; 软件开发技能点参照&#xff1a;软件开发&#xff0c;小白变大佬&#xff0c;这套学习路线让你少走弯路是认真的&#xff0c;欢迎讨论 软件开发技能点参照&#xff1a;Java学习完整路线&#xff…...

CnosDB 元数据集群 – 分布式时序数据库的大脑

CnosDB 是一个分布式时序数据库系统&#xff0c;其中元数据集群是核心组件之一&#xff0c;负责管理整个集群的元数据信息。 1. 概述 CnosDB 是一个分布式时序数据库系统&#xff0c;其中元数据集群是核心组件之一&#xff0c;负责管理整个集群的元数据信息。元数据包括数据库…...

白骑士的Matlab教学进阶篇 2.5 Simulink

Simulink是MATLAB的扩展工具&#xff0c;提供了一个图形化的建模和仿真环境。它广泛应用于系统设计、仿真、自动控制、信号处理等领域。本文将详细介绍Simulink的简介与基本使用、建立与仿真模型、控制系统设计与仿真、与MATLAB的集成。 Simulink简介与基本使用 什么是Simuli…...

linux安装anaconda

参考 如何在Linux服务器上安装Anaconda&#xff08;超详细&#xff09;_linux安装anconda-CSDN博客 官网 Index of / 安装网站 https://repo.anaconda.com/archive/Anaconda3-2024.06-1-Linux-x86_64.sh wget https://repo.anaconda.com/archive/Anaconda3-2024.06-1-Lin…...

python装饰器作用和使用场景

当谈到装饰器时&#xff0c;很多初学者很迷糊&#xff0c;有一个经典的例子可以帮助理解它们的作用。装饰器允许你在不修改函数代码的情况下&#xff0c;动态地改变函数的行为。 一、用法 假设我们有一个简单的函数&#xff0c;用来输出一条简单的问候语&#xff1a; 复制代码…...

Apache Tomcat 7下载、安装、环境变量配置 详细教程

Apache Tomcat 7下载、安装、环境变量配置 详细教程 Apache Tomcat 7下载Apache Tomcat 7 安装Apache Tomcat 7 环境变量配置启动 Apache Tomcat 7测试Tomcat7是否启动成功 Apache Tomcat 7下载 1、下载地址&#xff0c;找到Archives 链接: 官网下载地址 2、找到Tomcat 7&…...

SQL注入实例(sqli-labs/less-20)

0、初始页面 1、确定闭合字符 2、爆库名 3、爆表名 4、爆列名 5、查询最终目标...

Linux Shell面试题大全及参考答案(3万字长文)

目录 解释Shell脚本是什么以及它的主要用途 主要用途 Shell脚本中的注释如何编写? 如何在Shell脚本中定义和使用变量? Shell支持哪些数据类型? 什么是Shell的命令替换?请举例说明。 管道(pipe)和重定向(redirection)有什么区别? 如何在Shell脚本中使用条件语句…...

速盾:cdn优化静态资源加载速度机制

CDN&#xff08;Content Delivery Network&#xff09;是一种优化静态资源加载速度的机制。它通过在全球多个地点部署服务器&#xff0c;将静态资源缓存到离用户最近的服务器上&#xff0c;从而提高资源加载速度。 在传统的网络架构中&#xff0c;当用户访问一个网站时&#x…...

04.C++类和对象(中)

1.类的默认成员函数 默认成员函数就是用户没有显式实现&#xff0c;编译器会自动生成的成员函数称为默认成员函数。一个类&#xff0c;我们不写的情况下编译器会默认生成以下6个默认成员函数&#xff0c;需要注意的是这6个中最重要的是前4个&#xff0c;最后两个取地址重载不重…...

【代码随想录训练营第42期 Day23打卡 回溯Part2 - LeetCode 39. 组合总和 40.组合总和II 131.分割回文串

目录 一、做题心得 二、题目与题解 题目一&#xff1a;39. 组合总和 题目链接 题解&#xff1a;回溯 题目二&#xff1a;40.组合总和II 题目链接 题解&#xff1a;回溯 题目三&#xff1a;131.分割回文串 题目链接 题解&#xff1a;回溯 三、小结 一、做题心得 今天是代码随想录…...

书生.浦江大模型实战训练营——(三)Git基本操作与分支管理

最近在学习书生.浦江大模型实战训练营&#xff0c;所有课程都免费&#xff0c;以关卡的形式学习&#xff0c;也比较有意思&#xff0c;提供免费的算力实战&#xff0c;真的很不错&#xff08;无广&#xff09;&#xff01;欢迎大家一起学习&#xff0c;打开LLM探索大门&#xf…...

数据可视化Axure大屏原型制作分享

数据可视化大屏通过清晰、直观且易于理解的方式呈现大量复杂数据&#xff0c;已成为各行各业中不可或缺的工具。Axure作为一款功能强大的原型设计工具&#xff0c;为数据可视化大屏的制作提供了强大的支持和丰富的资源。 Axure RP 是一款强大的原型设计工具&#xff0c;非常适…...

Python3安装

更新镜像&#xff1a; yum -y install epel-release.noarch 1.安装Python3 [root18 ~]# yum -y install python3 2.查看版本&#xff1a; [root18 ~]# python3 --version Python 3.6.8 3.执行镜像包&#xff1a; pip3 install -i https://pypi.tuna.tsinghua.edu.cn/sim…...

基于Python的数据科学系列(4):函数

引言 在前几篇文章中&#xff0c;我们探讨了Python中的基本数据类型、列表、元组和字典。在本文中&#xff0c;我们将深入研究Python中的函数。函数是编程中非常重要的概念&#xff0c;它允许我们将代码组织成模块化、可重用的组件。通过学习如何定义和使用函数&#xff0c;我们…...

高频焊接设备配电系统无源滤波系统的设计

1、高频焊机系统谐波状况简介 变压器容量&#xff1a;S11-M-1600/10KVA&#xff08;105%&#xff09;/0.4KV 短路阻抗&#xff1a;3.9% 谐波负载情况&#xff1a;一台600KW高频焊接设备 型号&#xff1a;GGP600-0.3-HC 输入电压&#xff1a;380V 输出电压&#xff1a;0…...

模拟退火的

题目链接 体验乱调参数而看天意的奇特体验 #include<bits/stdc.h> using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<ll,ll> pii; const int inf0x3f3f3f3f; const int N1e510; const int mod1e97; //#define int long…...

为什么有的地方笔记本经常连不上wifi,而手机可以?

mm&#xff1a;程程&#xff0c;为什么我的笔记本在图书馆&#xff0c;老是连不上wifi&#xff1f;经常要手工连好几次&#xff0c;我的手机却没有这样的问题。 我&#xff1a;你先用手机热点连一下&#xff0c;我给你远程看一下吧。 mm&#xff1a;好了&#xff0c;我的远程代…...

组件化开发

iOS的组件化开发是一种将大型应用拆分成多个独立、可复用的组件的开发模式。每个组件负责完成特定的功能&#xff0c;并通过明确定义的接口与其他组件进行交互。这种开发模式有助于提高代码的可维护性、可读性和可扩展性&#xff0c;同时降低模块之间的耦合度。 组件化开发的概…...

java学习--文件

简介 文件,对我们并不陌生,文件是保存数据的地方,比如大家经常使用的word文档,txt文 件,excel文件 ... 都是文件。它既可以保存一张图片,也可以保持视频,声音 …. 文件流 常用的文件操作 创建文件的对象相关构造器和方法 示范 方式一&#xff1a; 方式二&#xff1a; 老师演示…...