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

【javaEE】多线程进阶(Part1 锁策略、CAS、synchronized )

目录

  • 前言/补充
        • 4. 描述一下线程池的执行流程和拒绝策略有哪些?【面试题!】
  • 一、常见锁策略
    • 一)乐观锁VS悲观锁
    • 二)读写锁VS普通互斥锁
    • 三)重量级锁VS轻量级锁
    • 四)自旋锁VS挂起等待锁
    • 五)公平锁VS非公平锁
    • 六)可重入锁VS不可重入锁
    • 七)小结(记住概念【面试常见!!】)
    • 常见面试题
    • 【补】synchronized
  • 二、CAS
    • 一)CAS简介
    • 二)CAS的实现
    • 三) CAS的应用
      • 1) 实现原子类:
      • 2)实现自旋锁:
    • 四)CAS的ABA问题
    • 相关面试题
  • 三、【synchronized原理】
    • 一)基本特点
    • 二) synchronized加锁的具体过程
    • 三)synchronized其他的优化手段
      • 锁消除
      • 锁粗化
    • 相关面试题
  • THINK


前言/补充

今天不学习,明天变垃圾

  1. 本文主要内容:常见锁策略、CAS【面试常见】和synchronized原理。
  2. 初阶part是工作中常用+面试中常考, 进阶part是工作中很少用+面试中常考
    (多线程中最常用的是synchronized!)
  3. 模拟实现【阻塞队列、定时器、线程池】:
    Main1-3
    (一定要自己实现!!)

4. 描述一下线程池的执行流程和拒绝策略有哪些?【面试题!】

参考:线程池执行流程及拒绝策略

答: ① 线程池执行流程:
当任务来了之后,线程池的执行流程是:先判断当前线程数是否大于核心线程数?如果结果为 false,则新建线程并执行任务;如果结果为 true,则判断任务队列是否已满?如果结果为 false,则把任务添加到任务队列中等待线程执行,否则则判断当前线程数量是否超过最大线程数?如果结果为 false,则新建线程执行此任务,否则将执行线程池的拒绝策略
线程池执行

② 拒绝策略:
当任务过多且线程池的任务队列已满时,此时就会执行线程池的拒绝策略,线程池的拒绝策略默认有以下 4 种:
AbortPolicy:中止策略,线程池会抛出异常并中止执行此任务;
CallerRunsPolicy:把任务交给添加此任务的(main)线程来执行;
DiscardPolicy:忽略此任务,忽略最新的一个任务;
DiscardOldestPolicy:忽略最早的任务,最先加入队列的任务。
默认的拒绝策略为 AbortPolicy 中止策略


一、常见锁策略

一)乐观锁VS悲观锁

  1. 实际开发中其实基本不会考虑锁策略,除非你要自己实现一个锁
  2. 要求:对于锁策略的名词有简单的了解就行,面试的时候也是考察概念(背)
  3. 乐观锁vs悲观锁:描述的是两种不同的加锁态度
  4. 乐观锁:预测锁冲突的概率不高,因此做的工作就可以简单一些。
    悲观锁:预测锁冲突的概率较高,因此做的工作就会复杂一些。
  5. 锁冲突:两个线程竞争同一把锁,此时就会产生锁冲突,然后线程就会进入阻塞等待。
  6. 如果面试中问到也建议多举例子(万全准备 VS 随遇而安)
  7. synchronized 初始使用乐观锁策略, 当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略。
  8. 乐观锁的一个重要功能就是要检测出数据是否发生访问冲突, 我们可以引入一个 “版本号” 来解决。(“版本号”其实就是修改次数)

二)读写锁VS普通互斥锁

  1. 普通的互斥锁就如同synchronized,即:两个线程竞争同一把锁,没抢占到锁的线程就会产生等待。
  2. 读写锁分两种情况:加读锁(访问)、加写锁(修改)
  3. 多个线程同时读同一个变量是互相不会有影响的,此时也就不必加锁。
  4. ① 读锁和读锁之间是不会产生竞争的
    ② 写锁和写锁之间是有竞争的
    ③ 读锁和写锁之间是有竞争的
    (在实际开发中,读的场景往往很多,而写的场景往往更少;所以读写锁相比于普通互斥锁就少了很多的锁竞争,优化了效率。)
  5. 读写锁就是把读操作和写操作区分对待, Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁。
  • ReentrantReadWriteLock.ReadLock 类表示一个读锁, 这个对象提供了 lock / unlock 方法进行加锁解锁.
  • ReentrantReadWriteLock.WriteLock 类表示一个写锁, 这个对象也提供了 lock / unlock 方法进行加锁解锁.
  1. 注意, 只要是涉及到 “互斥”, 就会产生线程的挂起等待; 一旦线程挂起, 再次被唤醒就不知道隔了多久了。因此尽可能减少 “互斥” 的机会, 就是提高效率的重要途径。
  2. 读写锁特别适合于 “频繁读, 不频繁写” 的场景中。 (这样的场景其实也是非常广泛存在的)。
  3. 注意: synchronized 不是读写锁!

三)重量级锁VS轻量级锁

  1. 轻量级锁就是加锁解锁的开销比较小;纯型的纯用户态的加锁逻辑就是开销比较小的
  2. 重量级锁就是加锁解锁的开销比较大;纯型的进入内核态的加锁逻辑就是开销比较大的
  3. 轻量级锁和重量级锁是站在结果的角度看待的,看最终加锁解锁操作消耗的时间是多还是少;而乐观锁和悲观锁是站在加锁的过程上去看待的,看加锁解锁过程中干的工作是多还是少。
  4. 通常情况下,干的工作多消耗的时间也较多。所以:乐观锁一般也是比较轻量,而悲观锁一般是比较重量的,但是不绝对。
  5. 锁的核心特性 “原子性”, 这样的机制追根溯源是 CPU 这样的硬件设备提供的
  • CPU 提供了 “原子操作指令”
  • 操作系统基于 CPU 的原子指令, 实现了 mutex 互斥锁.
  • JVM 基于操作系统提供的mutex互斥锁
    JVM实现了 synchronized 和 ReentrantLock 等关键字和类.
    原子性
    注: synchronized 并不仅仅是对 mutex 进行封装, 在 synchronized 内部还做了很多其他的工作。
  1. 重量级锁: 加锁机制重度依赖了 OS 提供了 mutex。
    (大量的内核态、用户态切换; 容易引发线程调度)
  2. 轻量级锁: 加锁机制尽可能不使用 mutex, 而是尽量在用户态代码完成, 实在搞不定了再使用 mutex.
    (少量的内核态用户态切换; 不太容易引发线程调度)
  3. synchronized 开始是一个轻量级锁, 如果锁冲突比较严重就会自动变成重量级锁。

四)自旋锁VS挂起等待锁

  1. 自旋锁是轻量级锁的一种典型实现:自旋就类似于“忙等”,需要消耗大量的CPU反复询问当前的锁是否已经准备就绪;但是能够第一时间知道准备就绪。(自旋锁是纯用户态实现的)
  2. 挂起等待锁是重量级锁的一种典型实现:此时可能会导致获取到锁的时间没那么及时,但是在等待的时间里CPU可以做别的事情。
  3. 自旋锁是一种典型的 轻量级锁 的实现方式。

优点: 没有放弃 CPU, 不涉及线程阻塞和调度, 一旦锁被释放, 就能第一时间获取到锁。
缺点: 如果锁被其他线程持有的时间比较久,那么就会持续的消耗 CPU 资源。 (而挂起等待的时候是 不消耗 CPU 的)。

  1. synchronized 中的轻量级锁策略大概率就是通过自旋锁的方式实现的.

五)公平锁VS非公平锁

  1. 公平锁:其实就是“先来后到”,就是先来的线程先使用
  2. 非公平锁:就是不管先后顺序直接去抢占
  3. 操作系统默认的锁的调度是非公平锁。但是如果要想实现公平锁就需要引入额外的数据结构来记录加锁的顺序,此时就需要一定的额外开销。
  4. synchronized 是非公平锁。

六)可重入锁VS不可重入锁

  1. 可重入锁:同一个线程针对同一把锁连续加锁两次,不会死锁
  2. 不可重入锁:同一个线程针对同一把锁连续加锁两次,会死锁
  3. 可重入锁(也叫做递归锁),允许同一个线程多次获取同一把锁。
  4. Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括
    synchronized关键字锁都是可重入的。
  5. 而 Linux 系统提供的 mutex 是不可重入锁。

七)小结(记住概念【面试常见!!】)

1.乐观锁Vs悲观锁
2.普通互斥锁 VS 读写锁
3.轻量级锁VS重量级锁
4.自旋锁VS挂起等待锁
5.公平锁VS非公平锁
6.可重入锁VS不可重入锁

常见面试题

  1. 你是怎么理解乐观锁和悲观锁的,具体怎么实现呢?
    答: ① 悲观锁认为多个线程访问同一个共享变量冲突的概率较大, 会在每次访问共享变量之前都去真正加锁
    ② 乐观锁认为多个线程访问同一个共享变量冲突的概率不大, 并不会真的加锁, 而是直接尝试访问数据。 在访问的同时识别当前的数据是否出现访问冲突。
    ③ 悲观锁的实现就是先加锁(比如借助操作系统提供的 mutex), 获取到锁再操作数, 获取不到锁就等待。
    乐观锁的实现可以引入一个版本号,借助版本号识别出当前的数据访问是否冲突。

  2. 介绍下读写锁?
    答: ① 读写锁就是把读操作和写操作分别进行加锁.
    ② 读锁和读锁之间不互斥.
    ③ 写锁和写锁之间互斥.
    ④ 写锁和读锁之间互斥.
    ⑤ 读写锁最主要用在 “频繁读, 不频繁写” 的场景中.

  3. 什么是自旋锁,为什么要使用自旋锁策略呢,缺点是什么?
    答: ① 自旋锁即:如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止。 第一次获取锁失败, 第二次的尝试会在极短的时间内到来; 一旦锁被其他线程释放, 就能第一时间获取到锁。
    ② 相比于挂起等待锁,
    优点: 没有放弃 CPU 资源, 一旦锁被释放就能第一时间获取到锁, 更高效. 在锁持有时间比较短的场景下非常有用。
    缺点: 如果锁的持有时间较长, 就会浪费 CPU 资源。

  4. synchronized 是可重入锁么?
    答: ① 是可重入锁.
    ② 可重入锁指的就是连续两次加锁不会导致死锁.
    ③ 实现的方式是在锁中记录该锁持有的线程身份, 以及一个计数器(记录加锁次数)。如果发现当前加锁的线程就是持有锁的线程, 则直接计数自增。

【补】synchronized

对于synchronized:
①既是乐观锁,也是悲观锁
②既是轻量级锁,又是重量级锁
③乐观锁的部分是基于自旋锁实现的,悲观锁的部分是基于挂起等待锁实现的

所以:
synchronized是自适应的:初始使用的时候是乐观锁/轻量级锁/自旋锁,如果锁竞争不激烈就保持上述状态不变;但是如果锁竞争激烈了,synchronized就会自动升级成悲观锁/重量级锁/挂起等待锁。

④不是读写锁,而是普通互斥锁
⑤是非公平锁
⑥是可重入锁
(在标准库中是有另外的其他锁能够实现④⑤的)


二、CAS

一)CAS简介

  1. CAS【 compare and swap】即比较并交换:把内存中的某个值M和CPU寄存器A中的值进行比较,如果两个值相同,就把另一个寄存器B中的值和内存的值M进行交换(把内存的值M放到寄存器B,同时把寄存器B的值写给内存;其实这是一个“写内存”操作,更关心的是交换后内存值M
    (返回值是是否操作成功)
  2. CAS伪代码:
boolean CAS(address, expectValue, swapValue) {
// address:内存地址
// expectValue:比较寄存器A
// swapValue:交换寄存器Bif (&address == expectedValue) {&address = swapValue;//这里的赋值其实就是“交换”。
//但是其实并不关心寄存器B里的是啥,更关心的是内存中是啥!
//所以把交换近似看成是赋值其实也没毛病。return true;}return false;
}// 以上的这一组操作是通过硬件实现的,一个CPU指令完成的,也就是说是原子性的!
// 则CAS是线程安全的!! 同时还高效。(高效:因为不涉及到锁冲突+线程等待)
// 故:基于CAS实现一些逻辑的时候即使不加锁也是可以实现线程安全的!
  1. 当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号.
  2. CAS 可以视为是一种乐观锁. (或者可以理解成 CAS 是乐观锁的一种实现方式)

二)CAS的实现

针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:

  • java 的 CAS 利用的的是 unsafe 这个类提供的 CAS 操作;
  • unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;
  • Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子性。

简而言之,是因为硬件予以了支持,软件层面才能做到

三) CAS的应用

  1. CAS只是在特定场景使用,加锁的适用面更广,并且加锁代码往往比CAS的可读性更好。
  2. CAS的操作是由CPU的一条指令原子性完成的,所以是线程安全的,效率也较高。(高效是因为没有锁冲突和线程等待)
  3. CAS最常用的两个场景:

1) 实现原子类:

① 如前面讲过的count++,在多线程环境下线程是不安全的,要想线程安全,就需要加锁,但是加锁后性能就会大打折扣;所以,我们就可以基于CAS操作来实现“原子”的++,从而保证线程的安全和高效。
② 标准库中提供了 java.util.concurrent.atomic 包, 里面的类都是基于这种方式来实现的。
典型的就是 AtomicInteger 类. 其中的 getAndIncrement 相当于 i++ 操作。
③ 伪代码的实现:

class AtomicInteger {
// AtomicYinteger : 这个其实是在标准库中已经封装好的一个类private int value;public int getAndIncrement() {// getAndIncrement() :这个方法就相当于后置++int oldValue = value;// 此处的oldValue相当于是寄存器A,是把内存中的value值读取到寄存器中!while ( CAS(value, oldValue, oldValue+1) != true) {// 把(oldValue+1)理解成时另外一个寄存器B的值// 比较看内存中的value值是否和寄存器A的值相同,如
//果相同就把寄存器B的值给设置到value内存中,同时
//CAS返回true,结束循环;
//如果不相同,就无事发生,CAS返回false,进入循环
//体里,重新读取内value值到寄存器A中。// 其实就类似于给内存做个标记,使用寄存器A来检查该内存值是不是之前的值,也就是有没有被修改。
//(寄存器A就是标记)!!oldValue = value;}return oldValue;}
}

④ 注:CAS 是直接读写内存的, 而不是操作寄存器。

2)实现自旋锁:

  1. 自旋锁是一个纯用户态的轻量级锁,当发现锁被其他线程持有的时候,另外的线程不会挂起等待,而是会反复询问,看当前的锁是否被释放了。(类似于“忙等”)(反复询问其实就是为了抢先执行,节省了进入内核和系统调度的开销
  2. 自旋锁伪代码:
public class SpinLock {private Thread owner = null;// owner : 当前这把锁是哪个线程获取到的,null就是锁是无人获取的状态/解锁状态。public void lock(){// 通过 CAS 看当前锁是否被某个线程持有. // 如果这个锁已经被别的线程持有, 那么就自旋等待. // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. while(!CAS(this.owner, null, Thread.currentThread())){// 比较owner和null是否相同(是否为解锁状态),如果是就进行交换,把当前调用lock的线程的值设置到owner里,相当于此时加锁成功,同时结束循环。
// 如果owner不为null,则CAS不进行交换,返回fasle,会进入循环,此时会立即再次发起判定。// 也就是如果锁没有线程占用就占用,如果被占用就反复询问,一解锁就及时加锁。}}public void unlock (){this.owner = null;}
}
  1. 自旋锁这种实现是属于消耗CPU资源的,但是换来的是可以第一时间获取到锁。如果当前预期锁的竞争不太激烈的时候(也就是预期在短时间内获取到锁),使用自旋锁就是合适的。
  2. 自旋锁是一个轻量级锁,也是一个乐观锁

四)CAS的ABA问题

  1. CAS的ABA问题【面试的时候谈到CAS,十有八九就会谈到ABA
    1)这是CAS的一个小缺陷
    2)在CAS中进行比较的时候,如果此时的寄存器A和内存M的值相同,你无法判定是内存M的值始终不变还是M变了,但是又变回来了
    3)CAS的ABA问题其实在大部分情况下也不是事儿,不会出现bug;但是在极端情况下是会出现问题的。
    4)ABA在极端情况下是啥效果?
    举例:可能会导致一次取钱,两次扣款(也就是在第一个线程完成取款之后,又有人转账,而第二个线程进行CAS检查时候发现数值相同,就进行扣款操作)
  2. 如何解决ABA带来的问题?
    只要有一个记录能够记录上内存的变化就可以解决ABA问题了。
    那么如何进行记录呢?
    ——另外搞一个内存,保存内存M的“修改次数”(版本号)或者是“上次修改时间”,通过这两种方法都是可以解决ABA问题的。
  3. “修改次数”“上次修改时间”都是【只增不减】,也就是说无法再跳回去。
    此时比较的就不是寄存器A和内存M了,而是比较版本号/上次修改时间。

相关面试题

  1. 讲解下你自己理解的 CAS 机制
    答:CAS全称 compare and swap, 即 “比较并交换”。 相当于通过一个原子的操作, 同时完成 “读取内存, 比较是否相等, 修改内存” 这三个步骤。本质上需要 CPU 指令的支撑。
  2. ABA问题怎么解决?
    答: ① 给要修改的数据引入版本号。在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期。
    ② 如果发现当前版本号和之前读到的版本号一致, 就真正执行修改操作, 并让版本号自增; 如果发现当前版本号比之前读到的版本号大, 就认为操作失败。

三、【synchronized原理】

【synchronized原理】主要讨论的是synchronized背后做的事情

一)基本特点

  1. synchronized的效果是“加锁”,当两个线程针对同一个对象加锁的时候就会出现锁竞争;后来尝试加锁的线程就得阻塞等待,直到前一个线程释放锁。
  2. synchronized 具有以下特性(只考虑 JDK 1.8):
    ① 开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁.
    ② 开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁.
    ③ 实现轻量级锁的时候大概率用到的自旋锁策略
    ④ 是一种不公平锁
    ⑤ 是一种可重入锁
    ⑥ 不是读写锁

二) synchronized加锁的具体过程

  1. JVM 将 synchronized 锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。会根据情况,进行依次升级。
  2. synchronized加锁的具体过程:
    1)偏向锁:其实就类似于“懒汉模式”,必要的时候才加锁,能不加就不加。(类似于出现锁冲突才真正加锁
    ① 偏向锁不是真加锁,而是只是设置了一个状态。(举例:搞暧昧)
    ② 无竞争,偏向锁;有竞争,轻量级锁; 竞争激烈,重量级锁。(这叫“锁升级/锁膨胀”,这是JVM实现synchronized的时候为了程序员方便而引入的一些优化机制)

2)轻量级锁
① 此处的轻量级锁就是通过 CAS 来实现。

通过 CAS 检查并更新一块内存 (比如 null => 该线程引用)
如果更新成功, 则认为加锁成功
如果更新失败, 则认为锁被占用,继续自旋式的等待(并不放弃 CPU).

② 自旋操作是一直让 CPU 空转, 比较浪费 CPU 资源.
③ 因此此处的自旋不会一直持续进行, 而是达到一定的时间/重试次数, 就不再自旋了。也就是所谓的 “自适应”。

3)重量级锁
① 如果竞争进一步激烈, 自旋不能快速获取到锁状态, 就会膨胀为重量级锁
② 此处的重量级锁就是指用到内核提供的 mutex .
③ 执行过程:

  • 执行加锁操作, 先进入内核态;
  • 在内核态判定当前锁是否已经被占用; 如果该锁没有占用, 则加锁成功, 并切换回用户态
  • 如果该锁被占用,则加锁失败,此时线程进入锁的等待队列, 挂起。 等待被操作系统唤醒。
  • 经历了一系列的沧海桑田, 这个锁被其他线程释放了, 操作系统也想起了这个挂起的线程, 于是唤醒这个线程, 尝试重新获取锁。
  1. synchronized更多的考虑到降低程序员的使用负担,内部就实现了轻量级锁和重量级锁的“自适应”操作。

如果当前场景中锁竞争不激烈,则是以轻量级锁状态来进行工作(轻量级锁是通过自旋来实现的,可以第一时间拿到锁);
如果当前场景中锁竞争激烈,则是以重量级锁状态来进行工作的(重量级锁通过挂起等待来实现,可能拿到锁每那么及时,但是节省了CPU的开销

三)synchronized其他的优化手段

synchronized还有其他的优化手段:(一是锁升级/所膨胀,二是锁消除,三锁粗化)

锁消除

  1. JVM自动判定此处的代码是否需要加锁,如果JVM判定此处的代码是不需要加锁的,但是你写了synchronized,此时就会自动把synchronized给去掉。
  2. 如:你只有一个线程 或者是 虽然有多个线程,但是多个线程并不涉及修改同一个变量,如果代码中也写了synchronized,此时synchronized加锁操作也直接会被JVM自动去掉。
  3. synchronized加锁的时候应该是先偏向锁,只是改个标志位,按理来说操作的开销应该是不大的,但是即使如此,能够消除的时候是连多余一点的开销都不想承担。
  4. 锁消除是一种编译器优化的行为,但是编译器的判定不是特别准,此时如果不是编译器有十足/100%的把握都是不会进行synchronized的自动消除的。 也就是说:锁消除只是在编译器/JVM有十足的把握的时候才进行
  5. 编译器+JVM 判断锁是否可消除. 如果可以, 就直接消除
  6. 有些应用程序的代码中用到了 synchronized, 但其实没有在多线程环境下. (例如 StringBuffer)此时每个 append 的调用都会涉及加锁和解锁, 但如果只是在单线程中执行这个代码, 那么这些加锁解锁操作是没有必要的, 白白浪费了一些资源开销。所以就会自动去掉锁。
    注:StringBuffer线程安全/自动加锁,StringBuilder线程不安全!!!

锁粗化

  1. 锁的粒度:synchronized对应的代码块中包含多少的代码,包含的代码越少则锁的粒度越细;包含的代码越多则锁的粒度越粗。
  2. 锁粗化:就是把细粒度的加锁变为粗粒度的加锁。(只能是对同一个对象加锁的代码!!)
    (锁粗化的前提是粗化前后代码逻辑不变)
  3. 一段逻辑中如果出现多次加锁解锁, 编译器 + JVM 会自动进行锁的粗化
  4. 实际开发过程中, 使用细粒度锁, 是期望释放锁的时候其他线程能使用锁。但是实际上可能并没有其他线程来抢占这个锁, 这种情况 JVM 就会自动把锁粗化, 避免频繁申请释放锁。

相关面试题

  1. 什么是偏向锁?
    答: 偏向锁不是真的加锁, 而只是在锁的对象头中记录一个标记(记录该锁所属的线程)。 如果没有其他线程参与竞争锁, 那么就不会真正执行加锁操作, 从而降低程序开销。 一旦真的涉及到其他的线程竞争, 再取消偏向锁状态, 进入轻量级锁状态。

  2. synchronized 实现原理 是什么?
    答: 参考【synchronized原理】所有内容:特点+加锁过程+优化手段。


THINK

  1. 描述一下线程池的执行流程和拒绝策略有哪些?【面试题!】
  2. 常见锁策略 + 面试题
  3. CAS + CAS的ABA问题 + 面试题
  4. synchronized原理(特点+加锁过程+优化手段)+面试题!
  5. 面试题超级重要!!!

相关文章:

Python 语音识别系列-实战学习-DFCNN_Transformer的实现

DFCNN_Transformer的实现 前言1.定义声学模型和获取数据的函数2.训练声学模型3.定义语言模型4.训练语言模型5.模型测试5.总结 前言 此博客是基于华为云中的DFCNN_Transformer的教程进行的学习和实践。本文将介绍一个结合了深度全卷积网络(DFCNN)和Trans…...

《QT实用小工具·五十二》文本或窗口炫酷有趣的滚动条——果冻条

1、概述 源码放在文章末尾 该项目实现了文本或窗口纤细的滚动条——果冻条 一个可以像弓弦一样拉出来,并且来回弹动的普通滚动条。 思路为此,但发现实际效果更像条状果冻,并且略有谐音, 故,称之为——“果冻条”&am…...

FreeRTOS任务通知

FreeRTOS任务通知 FreeRTOS 新增了任务通知(Task Notifictions)这个功能,可以使用任务通知来代替信号量、消息队列、事件标志组等这些东西。使用任务通知的话效率会更高,任务通知在 FreeRTOS 中是一个可选的功能, 使用队列、信号量、事件标志组时都需另外创建一个结构体,通…...

python web密码爆破脚本

如下 # -*- coding: utf-8 -*-肉机爆破密码脚本 由于是面向肉机的脚本,所以牺牲了一些速度,增加了准确性 程序挂肉机上然后去睡觉等结果嘛 从这里复制了代码:https://blog.csdn.net/tempulcc/article/details/108323499 对于没pip的机器,可把模块打包成zip,上传的机器上用unz…...

Unity 热更--AssetBundle学习笔记 1.0【AB包资源加载工具类的实现】

本文介绍AB包资源加载的6种方式,封装实现成单例工具类,方便在开发中进行调用。工具类封装 通过上文中对AB包加载API的了解和简单使用,对AB包资源加载的几种方法进行封装,将其写入单例类中,如代码展示。 确保每个AB资源包只加载一次: 在LoadAssetBundleManager 单例工具类…...

Qt开发工具使用

...

【javaEE】多线程进阶(Part1 锁策略、CAS、synchronized )

目录前言/补充4. 描述一下线程池的执行流程和拒绝策略有哪些?【面试题!】一、常见锁策略一)乐观锁VS悲观锁二)读写锁VS普通互斥锁三)重量级锁VS轻量级锁四)自旋锁VS挂起等待锁五)公平锁VS非公平…...

deepin(深度)系统下qt5.12.0的程序打包发布到linux云服务器上

做项目时要求,要求做一个用于QT客户端更新提供更新的服务器,服务器弄好啦,要测试一下,在发布时,发现了一些问题,在此记录一下。 这个打包和我的前一篇博客步骤一样,打包可参考https://blog.csd…...

精读大型网站架构:前端架构模块化的方法及困境,自研框架Trick

模块化的方法 网页和网页之间有很多相似或者相同的模块,模块化就是把这些模块抽离并独立管理。而模块化的方法,就是把模块的HTML、CSS和JavaScript文件独立出来,然后通过某种方法关联到使用这些模块的网页上。 在介绍模块化的具体方法之前&…...

用Python实现的这五个小游戏,你真的学会了嘛?

游戏名称1、五子棋 2、雷霆战机 3、贪吃蛇 4、坦克大战 5、俄罗斯方块 开发环境 Python版本:3.6.4 相关模块: pygame模块; 以及一些Python自带的模块。 环境搭建 安装Python并添加到环境变量,pip安装需要的相关模块即可。 一&am…...

linux环境下查询主板、CPU、内存等硬件信息

文章目录前言dmidecode常用参数-t参数测试-q参数测试-s参数测试总结前言 如果是在windows系统下,查询电脑硬件会容易的多,可以通过电脑属性、计算机管理等多种图形化界面中查到,如果安装了各种电脑管家,那查询这类信息就更方便了…...

查看日志.

如果查看比较小的日志文件:cat xxx.log 一般常用:view xxx.log/vi xxx.log查找关键字,如“木叶”:编辑,/木叶,确定,然后按“n”键就能往下找。 如果想往上找,输入:$到最后一行&#…...

vue3 生命周期函数,都改了啥?

vue2到3常用生命周期钩子函数的变化 Ⅰ. 实例化 和 数据初始化 (beforeCreate,created > setup) 1. new Vue 从开始 > 结束 [vue2和3 、两版本区别处] vue2的写法> export default {beforeCreate(){console.log(vue的实例 还没ne…...

基于springboot的医院管理系统

项目描述 临近学期结束,还是毕业设计,你还在做java程序网络编程,期末作业,老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里,你想解决的问题&#xff0…...

Django + Nginx https部署实战(第一辑)

WebServer和WebAPP 之前对于nginx的了解都只是听说,根本就不知道nginx对于整个网站的作用。经历了数个项目之后,我本人逐渐对nginx有了更深入的了解,也希望把这段经历拿出来分享给大家! 由于我本人之前接触的都是Python的Django…...

Pycharm+服务器运行代码

Pycharm服务器运行代码服务器的连接与Anaconda环境配置ssh连接安装Anaconda创建虚拟环境安装代码所需的库Pycharm上传代码到服务器服务器的连接与Anaconda环境配置 ssh连接 我使用的是MobaXterm,新建一个会话,选择SSH,输入主机IP地址自己的…...

【Spring】IDEAspring-mybatis的整合----关于配置文件的整合

文章目录spring-mybatis的整合过程步骤1.导包&#xff0c;spring的jar包&#xff0c;mybatis的jar包2.mybatis.xml配置3.spring-mybatis.xml配置4.dao、service层、代码测试spring-mybatis的整合过程步骤 1.导包&#xff0c;spring的jar包&#xff0c;mybatis的jar包 <!--统…...

ssm技术

ssm ssm框架配置 maven项目–》webquickstart pom文件 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLo…...

MQ消息队列

MQ消息队列 消息队列&#xff08;Message Queue&#xff0c;简称MQ&#xff09;&#xff0c;指保存消息的一个容器&#xff0c;本质是个队列 消息队列是大型分布式系统不可缺少的中间件&#xff0c;也是高并发系统的基石中间件 使用消息队列还可以实现异步处理 下图便是消息…...

【JVM技术专题】精心准备了一套JVM分析工具的锦囊「JConsole补充篇」

前提概要 本篇文章主要针对于之前本系列文章的补充版&#xff0c;之前落下了Jconsole分析工具&#xff0c;所以为了了却这个遗憾&#xff0c;所以小编又开了这篇文章&#xff0c;主要针对于Jconsole工具进行相关的应用性能分析。 初识JConsole 【Jconsole&#xff08;Java Moni…...

基于PHP的高效协同办公管理系统

有需要请私信或看评论链接哦 可远程调试 基于PHP高效协同办公管理系统一 介绍 高效协同办公管理系统基于Yii框架开发&#xff0c;数据库mysql&#xff0c;可以稳定用于商业以及门户级的开发和使用。 二 系统功能 用户 1 办公门户(邮件/日志/汇报/日程/信息中心/通知公告/微博…...

第十四届蓝桥杯(Web应用开发)模拟赛1期-大学组

数据类型检测 请看这篇数据类型检测 渐变色背景生成器 html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name&…...

【遥感科学】遥感科学绪论

第一章 绪论 本系列适用于梅安新老师的遥感导论复习&#xff0c;也可以作为遥感领域的快速入门文章 一、遥感的基本概念 啥子是遥感&#xff1f;借用童庆禧院士的理解&#xff0c;那就是欲穷千里目&#xff0c;更上一层楼&#xff0c;遥感可以看做人的眼睛或者感知的延伸&…...

Tensorflow图像识别 Tensorflow手写体识别(二)

资源介绍 我们从 MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges 这条链接&#xff08;MNIST官网&#xff09;中下载好数据集&#xff0c;如下&#xff1a; 下载下来以后整理成包含四个压缩包的文件MNIST_data&#xff08;不要解压&#x…...

盘点上海IB国际学校,你会选哪一所呢?

之前&#xff0c;小编给大家盘点了上海热门的AP学校和Alevel学校&#xff0c;同时也介绍了国际课程的具体情况&#xff1b;今天就和大家聊聊上海的IB国际学校。IB即是国际文凭组织IBO(International Baccalaureate Organisation)为全球学生开设从幼儿园到大学预科的课程&#x…...

OpenAI发布GPT-4.0使用指南

大家好&#xff0c;ChatGPT 自诞生以来&#xff0c;凭借划时代的创新&#xff0c;被无数人一举送上生成式 AI 的神坛。在使用时&#xff0c;总是期望它能准确理解我们的意图&#xff0c;却时常发现其回答或创作并非百分之百贴合期待。这种落差可能源于我们对于模型性能的过高期…...

Android性能优化面试题汇总

Android的性能优化涉及多个方面,如启动优化、稳定性优化、内存优化、网络优化、电量优化、安全优化等方面。 一、稳定性优化 1.1 你们做了哪些稳定性方面的优化 随着项目的逐渐成熟,用户基数逐渐增多,DAU持续升高,我们遇到了很多稳定性方面的问题,对于我们技术同学遇到…...

Linux系统配置JAVA环境

一、jar包下载 官网:https://www.oracle.com/java/technologies/downloads 二、文件上传 上传到linux服务器 解压 下面是解压的路径 三、修改profile文件 修改etc下的profile文件&#xff0c;添加以下内容 vim /etc/profileexport JAVA_HOME/root/java/jdk-17.0.11 expo…...

【Canvas与艺术】绘制地平线

【关键点】 灭点在透视中的作用。 【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>使用HTML5/Canvas绘制地平线<…...

算法——双指针

双指针经常服务于需要一边遍历数组&#xff0c;一边对数组元素进行改动的题目&#xff0c;有些人也称双指针为快慢指针。 同时双指针只是一种思想&#xff0c;实际做题时并不一定会真的采用指针。 移动零 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾…...

C语言中的指针常量和常量指针

指针常量和常量指针是C/C编程语言中两个重要的概念&#xff0c;它们都与指针有关&#xff0c;但具有不同的含义和用途。 1. 指针常量&#xff08;Pointer to Constant&#xff09; 指针常量指的是一个指针的值&#xff08;即它所指向的地址&#xff09;在初始化之后不能再被改…...