Synchronized、volatile、Lock

news/2024/5/17 15:21:46 标签: 多线程, , Android, Synchronized

  • 前言
  • 概述
  • 一、synchronized实现原理
  • 二、原理细节扩展
    • 1.上下文切换
    • 2.升级过程
  • 3.对象头
  • volatile
    • volatile概述:
      • volatile可见性实现原理
      • volatile有序性的实现原理
      • 受限原子性:
    • volatile与synchronize区别:
  • Lock
    • Lock的常用实现类
    • synchronized和lock的区别:
    • 可重入
    • 乐观
    • AQS(AbstractQueuedSynchronizer)
    • Condition
    • Sleep 、wait、yield 的区别,wait 的线程如何唤醒它?
    • 致谢
  • 相关
    • 解决方案
    • 那么当检测出死时,这些线程该做些什么呢?
  • 参考:

前言

1、 并行。多个cpu实例或多台机器同时执行一段代码,是真正的同时。
2、并发。通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。
3、线程安全。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。线程不安全就意味着线程的调度顺序会影响最终结果,比如某段代码不加事务去并发访问。
4、线程同步。指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如某段代码加入@synchronized关键字。线程安全的优先级高于性能优化。
5、原子性。一个操作或者一系列操作,要么全部执行要么全部不执行。数据库中的“事物”就是个典型的院子操作。
6、可见性。当一个线程修改了共享属性的值,其它线程能立刻看到共享属性值的更改。比如JMM分为主存和工作内存,共享属性的修改过程是在主存中读取并复制到工作内存中,在工作内存中修改完成之后,再刷新主存中的值。若线程A在工作内存中修改完成但还来得及刷新主存中的值,这时线程B访问该属性的值仍是旧值。这样可见性就没法保证。
7、有序性。程序运行时代码逻辑的顺序在实际执行中不一定有序,为了提高性能,编译器和处理器都会对代码进行重新排序。前提是,重新排序的结果要和单线程执行程序顺序一致。

详细查看:Android 多线程实现方式及并发与同步

概述

synchronized解决什么问题?
synchronized 是开发中解决同步问题中最常见,也是最简单的一种方法。

synchronized作用域:
方法同步和代码块同步。

synchronized 常用的三个作用:
原子性
即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行;
可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值;
有序性
防止编译器和处理对指令进行重排序,即也就是抑制指令重排序;

一、synchronized实现原理

synchronizrd同步实现原理?
synchronzied 在 jvm 里的实现都是基于进入和退出 Monitor 同步机制来实现方法同步和代码块同步,虽然具体实现细节不一样,但都是通过 成对 的 MonitorEnter 和 MonitorExit 指令来实现。

1.字节码层面实现:
代码块同步:直接通过指令 monitorenter 和 monitorexit 来实现,编译成字节码时,会在同步代码块的开始位置插入MonitorEnter 指令。
方法同步:在方法中增加synchronzied 修饰,会在字节码常量池中增加 ACC_SYNCHRONIZED 标识符,JVM 就是根据该标识符来实现方法的同步,当方法被调用时,执行线程将先获取 monitor ,获取成功之后才能执行方法体,方法执行完后再释放 monitor 。在方法执行期间,其他任何线程都无法再获得同一个 monitor 对象。
方法同步跟字节码同步实现细节不同,但最终都是通过Monitor 同步机制来实现。

示例代码:
在这里插入图片描述
字节码编译:
在这里插入图片描述
2.Java虚拟机对的细节处理
synchronized 的 状态 存储在对象头的MarkWord 中,在对象运行变化的过程中,的状态存在4种变化状态,即 无状态 、偏向状态 、轻量级状态 、重量级状态 。它会随着竞争情况逐渐升级,主要目的是为了提高获得和释放的效率。

注:
对象头由两个部分组成,分别是 KlassPoint 与 Mark Word 。

在jdk1.6之后,synchronizrd 得到了优化(无状态 ->偏向状态 ->轻量级状态->重量级),而添加各种的目的都是为了避免直接加而导致的上下文切换从而引发的耗时浪费。
不同之间的比较:
在这里插入图片描述
状态:没有线程访问的时候,对象无需加
偏向状态:顾名思义,它会偏向第一个访问的线程,当有一个线程进入同步块的情况下,就会给线程加一个偏向
(如果在运行过程中,同步只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向,从而减少加/解 的一些 CAS 操作)
轻量级:当第二个线程加入争用的时候,偏向就会升级为轻量级
(轻量级,会让没有获取到的其它线程进入自旋状态,避免线程阻塞造成的线程切换,减少系统调用引起的内核态与用户态切换,从而减少性能消耗)
重量级:轻量级不断自旋膨胀之后,达到设定阈值之后,就会升级为重量级。(重量级会让没有获取到的其它线程进入阻塞状态)
ps:重量级时依赖对象内部的 monitor 来实现,而 moitor 又依赖系统的 MutexLock(互斥) 来实现,所以重量级也被称为 互斥

为什么说重量级开销比较大?
当系统检查到时重量级时,会把正在等待获取的线程进行阻塞,被阻塞的线程不会消耗 cpu ,但是阻塞和唤醒线程,都需要操作系统来处理,这就需要从用户态转换到内核态,而从用户态到内核态的切换吗,需要通过系统调用来完成。

系统调用的过程中会发生 cpu 上下文切换,一次系统调用的过程,需要发生 两次 上下文切换(挂起时、重新争夺)。而这个过程很多时候比同步代码块所需时间还长(每一次上下文切换的过程为 3~5微秒 ,而cpu执行一条指令只需要 0.6ns)。

二、原理细节扩展

1.上下文切换

当我们某个资源使用 synchronizrd 进行加时:
1.当线程A获取了,线程B在获取时将会被阻塞,也即是 BLOCKED 状态,此时线程B暂停被操作系统 切出 ,操作系统会保存此时的上下文;
2.当线程A释放了,此时假设线程B获取到了,线程B 从 BLOCKED 进入 RUNNABLE 状态,即线程重新唤醒,此时线程将获取上次操作系统保存的上下文继续执行。
上述的过程中线程B执行了 两次 上下文切换,每一次上下文切换的过程为 3~5微秒 ,而cpu执行一条指令只需要 0.6ns ,所以如果加后只是执行几条普通指令,如某个变量的自增或者其他,那么上下文切换将对性能产生极大影响,所以在jdk1.6以后,synchronizrd 得到了优化,新增了几种,以及不同情况下的状态变化,以避免直接重量级产生的性能损耗。
关于上下文可参考:Java性能之线程上下文切换究极解析(https://zhuanlan.zhihu.com/p/82848203)

2.升级过程

在这里插入图片描述
偏向的撤销,需要等待 全局安全点(即在这个时间点上没有字节码正在执行),它会首先暂停拥有的线程,判断对象是否处于被定状态,撤销偏向后恢复到未定(标志位为 “01” ) 或轻量级(标志位为 “00” )的状态。
在这里插入图片描述
ps:Java的乐观都是通过CAS实现

3.对象头

对象头中mark word的格式:
在这里插入图片描述

volatile

volatile字译:挥发性的,不稳定性的

volatile会为域变量的访问提供了一种免机制。使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,因此每次使用该域就要重新计算,而不是使用寄存器中的值 。

在介绍volatile语义实现原理之前,我们先来看两个与CPU相关的专业术语:
内存屏障(memory barriers):一组处理器指令,用于实现对内存操作的顺序限制。
缓存行(cache line):CPU高速缓存中可以分配的最小存储单位。处理器填写缓存行时会加载整个缓存行。

volatile概述:

1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2.禁止进行指令重排序。
3.不能保证原子性

volatile可见性实现原理

当写instance这个volatile变量时,发现add前面加个一个lock指令,volatile可见性的实现就是借助了CPU的lock指令,通过在写volatile的机器指令前加上lock前缀,使写volatile具有以下两个原则:
1、写volatile时处理器会将缓存写回到主内存。
2、一个处理器的缓存写回到内存会导致其他处理器的缓存失效。

具体实现:
缓存一致性协议。即在一个处理器将自己缓存行的数据写回到系统内存后,其他的每个处理器就会通过嗅探在总线上传播的数据来检查自己缓存的数据是否已过期,当处理器发现自己缓存行对应的内存地址的数据被修改后,就会将自己缓存行缓存的数据设置为无效,当处理器要对这个数据进行修改操作的时候,会重新从系统内存中把数据读取到自己的缓存行重新缓存。

volatile有序性的实现原理

通过内存屏障来实现禁止指令重排。
详细查看volatile底层实现原理

受限原子性:

这里volatile变量的原子性与synchronized的原子性是不同的,synchronized的原子性是指只要声明为synchronized的方法或代码块儿在执行上就是原子操作的。而volatile是不修饰方法或代码块儿的,它用来修饰变量,对于单个volatile变量的读/写操作都具有原子性,但类似于volatile++这种复合操作不具有原子性。所以volatile的原子性是受限制的。并且在多线程环境中,volatile并不能保证原子性。

volatile与synchronize区别:

作用域不同:
Sychronized对整个方法或代码块起作用,
Volatile的是针对单个volatile变量的读写操作
量级不同:
Volatile相对Synchronized来说在同步上比较轻量级,能够有效降低CPU频繁的线程上下文切换和调度。
作用不同:
Synchronized具有原子性、可见性、禁止指令重排(有序性)
volatile只保证了变量的可见性与禁止指令重排,并不能保证原子性。
内部实现机制不同:
Sychronized:内置
Volatile:Lock指令

Lock

ReentrantLock能够代替synchronized关键字完成独占的功能,并且允许占有线程的重入,显示地调用lock、unlock方法使得代码更灵活,收缩性更好。

Lock使用更加灵活:
1.Lock.tryLock进行主动加
2.lock.unLock进行释放

一般而言,java中是在多线程环境下访问共享资源的一种手段。
一般的可以防止多个线程同时访问共享资源,但是也有些可以允许多个线程并发的访问共享资源,比如读写

在java5之前,要实现,只有依靠synchronized关键字来隐式的获取和释放
在java5中,提供了另一种显式地获取和释放的技术。可以通过接口java.util.concurrent.locks.Lock来实现。
jdk技术:早期重量级synchronized---->ReentrantReadWriteLock读写---->ReentrantLock---->jdk1.6优化后的synchronized

Lock的常用实现类

一、读写 ReentrantReadWriteLock
读写维护了一对,一个读和一个写
一般情况下,读写的性能都会比排它好,因为大多数场景读是多于写的。在读多于写的情况下,读写能够提供比排它更好的并发性和吞吐量。
Java中读写频率比:3:1 还是 10:1

二、ReentrantLock
使用:

Lock  reentrantLock   =  new ReentrantLock();
reentrantLock.lock();
reentrantLock.unlock();

实现原理:
显示的实现,线程一次,计数器就会增加一次,重入一次增加一次,释放一次就累减一次,计数器为0,则代表当前线程已经释放该了。
内部实现是基于GUC包下的AQS来实现的。

synchronized和lock的区别:

在这里插入图片描述
1、synchronized和lock的使用区别
synchronizedd是java底层支持的,托管给JVM执行的,即监视器(monitor)。而lock则是jdk实现。

synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要的对象。

lock:一般使用ReentrantLock类做为。在加和解处需要通过lock()和unlock()显示调用,使用更加灵活。

2、synchronized和lock的底层实现区别:
synchronized的底层也是一个基于CAS操作的等待队列,但JVM实现的更精细,把等待队列分为ContentionList和EntryList,目的是为了降低线程的出列速度;当然也实现了偏向,从数据结构来说二者设计没有本质区别。但synchronized还实现了自旋,并针对不同的系统和硬件体系进行了优化,而Lock则完全依靠系统阻塞挂起等待线程。

3、synchronized和lock性能区别
2种机制的具体区别:
synchronized原始采用的是CPU悲观机制,即线程获得的是独占。独占意味着其他线程只能依靠阻塞来等待线程释放。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争的时候,会引起CPU频繁的上下文切换导致效率很低。

而Lock用的是乐观方式。所谓乐观就是,每次不加而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。

现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。

4、synchronized和lock用途区别
synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候。

1.某个线程在等待一个的控制权的这段时间需要中断
2.需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程
3.具有公平功能,每个到来的线程都将排队等候

先说第一种情况,ReentrantLock的lock机制有2种,忽略中断和响应中断,这给我们带来了很大的灵活性。比如:如果A、B 2个线程去竞争,A线程得到了,B线程等待,但是A线程这个时候实在有太多事情要处理,就是一直不返回,B线程可能就会等不及了,想中断自己,不再等待这个了,转而处理其他事情。这个时候ReentrantLock就提供了2种机制:可中断/可不中断
第一,B线程中断自己(或者别的线程中断它),但是ReentrantLock不去响应,继续让B线程等待,你再怎么中断,我全当耳边风(synchronized原语就是如此);
第二,B线程中断自己(或者别的线程中断它),ReentrantLock处理了这个中断,并且不再等待这个的到来,完全放弃。

可重入

公平可以理解为排队拿,先来后到,非公平则靠CPU调度决定。
可重入:ReentrantLock,synchronizrd
可重入指的是当某个线程调用某个方法或者对象获取了一把时,再次调用了指定方法,导致的的重入。即本身已经获取到了,又一次经历了的获取,一般情况下,我们会在再次进入时判断当前线程是否获取了,如果获取了,就修改同步状态,即 AQS 中的 state+1 。为什么要state+1 ,因为释放的时候需要-1啊。

若一个程序或子程序可以“安全的被并行执行(Parallel computing)”,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,可以再次进入并执行它(并行执行时,个别的执行结果,都符合设计时的预期)。可重入概念是在单线程操作系统的时代提出的。

乐观

乐观就是乐观的认为:每次去拿数据的时候都认为别人不会修改,觉得不值得上,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观。在Java中JUC包下面的原子变量类就是使用了乐观的一种实现方式CAS实现的(CAS+volatile)。

两种乐观实现方式
1、版本号控制
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
2、CAS
即compare and swap(比较与交换),是一种有名的无算法。无编程,即不使用的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。

使用场景
乐观是适用于读多写少的场景的,那为什么呢? 如果写多会出现什么情况呢?因为CAS在失败的时候,是会不断重试的,如果多个线程竞争,那就会存在很多重试,CPU就不断空转,反而是消耗了性能,还不如直接上

缺点
1、ABA问题
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。
JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
2、循环等待时间过长(写操作较多)
自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
3、只能保证单个变量的原子操作
CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。

CAS与Sychronized的取舍:
简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)

  1. 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
  2. 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

总结:
Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 “重量级” 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得和释放带来的性能消耗而引入的 偏向 和 轻量级 以及其它各种优化之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继续竞争,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

AQS(AbstractQueuedSynchronizer)

并发的基本构建,java.util.concurrent类的许多阻塞类,例如ReentrantLock、Semaphore、ReentrantRead-WriteLock、CountDownLatch、SynchronousQueue和FultureTask等都是基于AQS构建的。
AQS内部维护了一个等待线程队列,其中记录了某个线程请求的是独占访问还是共享访问(condition)。
内部使用了 int state来表示同步状态,内部还维护了一个队列,来完成线程获取资源的一个排队工作,AQS是CLH队列的一种变体实现。 如果自定义一个类似的,我们一般会写一个子类继承自AQS,实现AQS的抽象方法来管理同步状态,例如tryAcquire、tryRelease

Condition

Lock和synchronized对应,可以实现线程同步
Condition和wait()/notify()等监视器方法对应,可以实现线程通信
synchronized是隐式地获取/释放,Lock是显式地获取释/放
CAS用于乐观的实现,Condition用于悲观的实现

在这里插入图片描述

Sleep 、wait、yield 的区别,wait 的线程如何唤醒它?

sleep、yield不会释放
sleep 暂停线程的执行,休眠,可打断,等休眠时间一过,才有执行权资格,但是只有又有了资格,不代表马上就能被执行,什么时候执行取决于操作系统的调用
wait 线程间的交互,消费者观察者常用,等待别人来唤醒,唤醒后,才有执行权的资格
yield 让出CPU执行权,进入到可执行状态

致谢

https://blog.csdn.net/hefenglian/article/details/82383569 区别
https://blog.csdn.net/qq_29373285/article/details/85964460 Lock原理
https://blog.csdn.net/Ryanqy/article/details/105351282 轻/重量级

经典:https://www.jianshu.com/p/9e6e84f15b95
https://blog.csdn.net/weixin_40378837/article/details/109017236 乐观

相关

在这里插入图片描述

产生的4个必要条件:
互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, …, pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, …, n-1),Pn等待的资源被P0占有。

解决方案

三种用于避免死的技术:

顺序(线程按照一定的顺序加
时限(线程尝试获取的时候加上一定的时限,超过时限则放弃对该的请求,并释放自己占有的
检测

那么当检测出死时,这些线程该做些什么呢?

一个可行的做法是释放所有,回退,并且等待一段随机的时间后重试。这个和简单的加超时类似,不一样的是只有死已经发生了才回退,而不会是因为加的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批,它们还是会重复地死(原因同超时类似,不能从根本上减轻竞争)。

一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死一样继续保持着它们需要的。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死发生的时候设置随机的优先级。

参考:

https://mp.weixin.qq.com/s/iYZXtqFWgqf4sWSpMCIrLA
https://juejin.cn/post/6902444047022391304
https://www.jianshu.com/p/43e8e3a8b688
https://www.freesion.com/article/98331106579


http://www.niftyadmin.cn/n/5024071.html

相关文章

Latex之在作者名字后面加上OCRID的图标

\usepackage{orcidlink} \author{Bob\textsuperscript{\orcidlink{0000-0000-0000-0000}}}效果如图

SP2-1503|0152:CMD窗口的SQLPLUS命令无法登录Oracle

场景还原 今天有小伙伴把Oracle卸载后重新安装,尝试以下三种方案均无法登录数据库 1.、在使用PLSQL Developer时,输入账号密码机械能登录操作,弹出空白弹框界面 即没有任何提示错误代码 只有一个白板的框 2、利用自身的SQL PLUS登录直接窗…

Hbase批量删除数据

一、TTL机制 HBase的TTL(Time To Live)是一种用于指定数据存活时间的机制。它允许用户为HBase中的数据设置一个固定的生存时间,在达到指定的时间后,HBase会自动删除这些数据。 具体操作如下: 三步走,先禁用…

连nil切片和空切片一不一样都不清楚?那BAT面试官只好让你回去等通知了。

连nil切片和空切片一不一样都不清楚?那BAT面试官只好让你回去等通知了。 问题 package mainimport ("fmt""reflect""unsafe" )func main() {var s1 []ints2 : make([]int,0)s4 : make([]int,0)fmt.Printf("s1 pointer:%v, s2 p…

QT应用启动失败排查方法

背景 启动QT应用经常会碰到应用启动失败,qt platform plugin无法启动,比如: qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found. This application failed to start becaus…

图论第二天|岛屿数量.深搜版、岛屿数量.广搜版、岛屿的最大面积、1020.飞地的数量

岛屿数量.深搜版 文档讲解 &#xff1a;代码随想录 - 岛屿数量.深搜版 状态&#xff1a;开始学习。 本题是dfs模板题 本题代码&#xff1a; class Solution { private:int dir[4][2] {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向void dfs(vector<vector<char>>&…

多层全连接网络:实现手写数字识别50轮准确率92.1%

多层全连接网络&#xff1a;实现手写数字识别50轮准确率92.1% 1 导入必备库2 torchvision内置了常用数据集和最常见的模型3 数据批量加载4 绘制样例5 创建模型7 设置是否使用GPU8 设置损失函数和优化器9 定义训练函数10 定义测试函数11 开始训练12 绘制损失曲线并保存13 绘制准…

git 的三种回滚方式

--hard 清空所有修改,删除本地数据 --soft 将之前提交的内容恢复到暂存区,不会修改本地文件 --mixed 将之前提交的内容恢复到未暂存状态,不会修改本地文件 (默认) 对本地代码库进行回滚 git log //查看提交历史&#xff0c;找出要回滚到的c…