Java并发04之线程同步机制

文章目录

  • 1 线程安全
    • 1.1 线程安全的变量
    • 1.2 Spring Bean
    • 1.3 如果保证线程安全
  • 2 synchronized关键字
    • 2.1 Java对象头
      • 2.1.1 对象组成部分
      • 2.1.2 锁类型
      • 2.1.3 锁对象
    • 2.2 synchronized底层实现
      • 2.2.1 无锁状态
      • 2.2.2 偏向锁状态
      • 2.2.3 轻量级锁状态
      • 2.2.4 重量级锁
      • 2.2.5 锁类型总结
      • 2.2.6 锁撤销
    • 2.3 synchronized失效场景
      • 2.3.1 失效场景1
      • 2.3.2 失效场景2
      • 2.3.3 失效场景3
  • 3 volatile关键字
    • 3.1 Java内存模型
      • 3.1.1 Java内存模型
      • 3.1.2 JMM三大特性
      • 3.1.3 happens-before原则
      • 3.1.4 as-if-serial语义
    • 3.2 volatile关键字
      • 3.2.1 volatile作用
      • 3.2.2 为什么说volatile无法保证原子性
    • 3.3 对比synchronized和volatile
    • 3.4 再看双重校验锁
  • 4 ReentrantLock
    • 4.1 可重入锁
    • 4.2 对比synchronized和ReentrantLock
  • 5 锁模型总结
    • 5.1 公平锁和非公平锁
    • 5.2 独享锁和共享锁
    • 5.3 乐观锁和悲观锁

1 线程安全

1.1 线程安全的变量

  • 成员变量和静态变量:如果它们没有共享,则线程安全。如果它们被共享了,但只有读操作,则线程安全。如果它们被共享了,且有读写操作,则需要考虑线程安全。
  • 局部变量:局部变量是线程安全的,但局部变量引用的对象则不一定是安全的。如果局部变量引用的对象没有逃离方法的作用范围,那么就是线程安全的。如果该对象逃离方法的作用范围,需要考虑线程安全。

1.2 Spring Bean

Spring容器本身并没有提供Bean的线程安全策略,因此Spring Bean对象(singleton作用域)本身不具备线程安全的特性。Controller、Service对象一般都是单例的,在并发环境下会涉及到线程安全问题。因此,它们通常是被设计为无状态对象,从而保证该对象是线程安全的。

无状态对象,就是没有成员变量的对象,或者成员变量也是一个无状态对象。因此,无状态对象不能保存可变数据,是线程安全的。在实际开发中,Controller、Service对象通常不会保存共享数据,而是提供对共享数据进行操作的方法。

1.3 如果保证线程安全

  • 使用锁机制保证读写操作的同步性,如synchronized、ReentrantLock。
  • 使用juc包的并发集合或原子类,如ConcurrentHashMap、AtomicInteger,这些工具类本质上也是使用锁机制、AQS队列或CAS做到读写操作的同步性。
  • 使用ThreadLocal提供变量的线程本地副本,实现变量的线程隔离。

2 synchronized关键字

2.1 Java对象头

2.1.1 对象组成部分

一个对象包括对象头,实例数据和填充数据。其中,对象头又包括三部分:

  • Mark Word:存储了对象的哈希码、分代年龄和锁信息。本小节主要是介绍Mark Word。
  • 类元数据地址:存储到对象类型数据的指针。
  • 如果当前对象是数组的话,还会存储数据的长度。

在这里插入图片描述

2.1.2 锁类型

对象头中的Mark Word存储了锁类型的相关信息,根据锁对象状态的不同,可以分为5类:

  • 无锁状态:偏向锁标志0,锁标志位01
  • 偏向锁状态:偏向锁标志1,锁标志位01
  • 轻量级锁状态:偏向锁标志无,锁标志位00
  • 重量级锁状态:偏向锁标志无,锁标志位10
  • GC标记:偏向锁标志无,锁标志位11

2.1.3 锁对象

  • 对于普通同步方法,锁是当前实例对象
  • 对于静态同步方法,锁是当前类的Class对象
  • 对于同步方法块,锁是Synchonized关键字括号里配置的对象

2.2 synchronized底层实现

JDK 6之前,synchronized解决的是多个线程之间访问资源的同步性,被它修饰的方法或者代码块在任意时刻只能有一个线程执行。synchronized同步代码块的实现使用的是monitorenter和 monitorexit指令,分别插入到代码块的开始和结束位置。任何锁对象都有一个monitor对象与之关联,当线程执行到monitorenter指令时,将会尝试获取锁对象所对应的monitor的所有权,获得monitor的所有权也就意味着获得了同步锁。如果获取成功,就会进入monitor的所有者Owner里面,如果锁被占用了,则会进入EntryList列表,变成阻塞状态。

JDK 6之后,为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。此时锁一共有4种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁,这几个状态会随着竞争情况逐渐升级,并且锁可以升级但不能降级。
在这里插入图片描述

2.2.1 无锁状态

当没有被当做锁的时候,就是个普通对象。此时对象头里锁标志位为01,偏向锁标志为0。

2.2.2 偏向锁状态

为了让获得锁的代价更低引入了偏向锁。当一个线程获取锁时,锁自动升级为偏向锁,会在锁的Mark Word里存储线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试是否仍然为同一线程。如果测试成功,表示线程已经获得了同步锁。

2.2.3 轻量级锁状态

如果锁的Mark Word中存储的线程ID不是当前想要竞争锁的线程ID,此时当前线程会尝试使用CAS去替换锁的Mark Word中存储的线程ID,如果替换成功,表示之前的线程已经执行结束了,锁仍然为偏向锁。如果竞争失败,表示当前锁发生了竞争,锁会升级为轻量级锁。
在这里插入图片描述

  • JVM首先会在当前线程的栈帧中开辟锁记录空间Lock Record,并将对象头中的Mark Word复制到Lock Record中,这个过程官方称为Displaced Mark Word
  • 然后尝试使用CAS将Mark Word替换为指向Lock Record的指针,如果竞争成功,代表线程抢到了锁。如果失败,表示锁已经被占有,就会不断自旋。

总结:在某个线程获得轻量级锁的过程中,会将Mark Word中的信息保存到该线程的栈中一块称之为Lock Record的地方,然后再将锁的对象头中的Mark Word替换一个指针,该指针指向Lock Record。如果说偏向锁只是一种线程和锁对象弱关联的关系,那么,轻量级锁就会升级为强关联的关系,即线程和锁对象“你中有我,我中有你”。

2.2.4 重量级锁

当轻量级锁竞争激烈时,会产生过多的自旋现象。因此引入了排队机制,即使用Monitor对象以及WaitSet和EntryList。但锁升级为重量级锁后,未竞争到锁的线程,不会再进行自旋,而是进入了EntryList队列中进行排队。
在这里插入图片描述

重量级锁的本质:如果给Java对象使用了synchronized并加上了重量级锁,那么这个对象就会关联一个Monitor对象,在Mark Work中会有一个指针指向这个Monitor对象。

  • Owner:当前锁的持有者,Owner只能指向一个线程。
  • WaitSet:等待队列,是之前获得过锁,但是条件不满足后,又退出锁的拥有,等待条件。
  • EntryList:被阻塞的队列,在Owner指向的对象不为空的时候,有其他线程想要获得锁,那么会进入EntryList队列。
  • WaitSet和EntryList的区别:EntryList执行条件都满足了,只需要获得锁。WaitSet是条件不满足,如果条件满足的话,重新进入到EntryList进行锁的竞争。

2.2.5 锁类型总结

当一个线程想要获得锁对象时,就会发现锁对象的Mark Work并不是存储null、哈希码和分代年龄(无锁),也不是存储线程指针、Epoch和分代年龄(偏向锁),也不是存储指向某一线程的Lock Record,而是存储了一个指向一个Monitor对象的指针,那么这个线程就会知道当前锁是一个重量级锁。此时,线程就会跑到Monitor对象的EntryList中,等待获得锁。

  • -XX:+UseBiasedLocking 开启偏向锁
  • -XX:BiasedLockingStartupDelay=0 关闭偏向锁延迟
  • -XX:+UseSpinning 开启轻量级锁
  • -XX:PreBlockSping 轻量级锁自旋次数

2.2.6 锁撤销

  • 偏向锁撤销:偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态。
  • 轻量级锁撤销:使用CAS尝试将线程栈中的锁记录复制的Displaced Mark Word替换锁的Mark Word,如果替换成功,则轻量级锁释放成功。

2.3 synchronized失效场景

2.3.1 失效场景1

synchronized修饰非static方法时,锁对象是当前对象。如果这个类是非单例的,会导致锁失效!例如:

  • 在Spring程序中,bean设置为prototype
  • 未被Spring管理的类,调用其普通同步方法同样会失效

解决方案:

  • 使用单例模式
  • 未被Spring管理的类,如某些工具类,应该尽可能使用静态同步方法
  • 使用分布式锁

2.3.2 失效场景2

Spring的@Transcational事务管理使用AOP方式实现,即在方法的执行开始和结束插入事务开启和提交或者回滚命令。

如果一个同步方法上有事务管理,如果一个线程在释放锁但还未提交事务时,另一个线程获取到锁并执行方法,可能会导致读取脏数据。

2.3.3 失效场景3

synchronized的锁时当前对象,因此也称为进程锁,即只能保证一个进程内的读写操作同步。当前我行应用通常是多实例部署,如果操作的共享数据是下游数据,如数据库数据等等,那么进程锁synchronized是不起作用的。

解决方案:使用分布式锁

3 volatile关键字

3.1 Java内存模型

3.1.1 Java内存模型

JMM(Java Memory Model):Java内存模型,是JVM规范中所定义的一种内存模型,它屏蔽掉了底层不同计算机的区别。JMM规定:所有的共享变量都存储于主内存,每一个线程有自己的本地内存,用于存储共享变量的副本。线程对变量的所有的操作都必须在本地内存中完成,而不能直接读写主内存中的变量,操作完成后再将共享变量刷写回主内存。
在这里插入图片描述

3.1.2 JMM三大特性

  • 可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。Java中普通的共享变量不保证可见性,因为每个线程本地内存中的副本数据修改后被写入内存的时机是不确定的,多线程并发下很可能出现"脏读"。(解决方案:synchronized、volatile)
  • 原子性:指一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰。(解决方案:synchronized)
  • 有序性:为了提高性能,编译器和处理器通常会对指令序列进行重新排序。指令重排可以保证串行语义一致,但不保证多线程间的语义也一致,即可能产生"脏读"。(解决方案:volatile)

3.1.3 happens-before原则

happens-before(先行发生原则)的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。

  • 次序原则:一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作;
  • 锁定规则:锁的获取的先后顺序;
  • volatile变量规则:对一个volatile变量的读写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的。
  • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  • 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作
  • 线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;可以通过Thread.interrupted()检测到是否发生中断。
  • 线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过isAlive()等手段检测线程是否已经终止执行。
  • 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始

3.1.4 as-if-serial语义

as-if-serial语义的意思是:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。

为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。

在单线程程序中,对存在控制依赖的操作重排序,不会改变执行结果(这也是as-if-serial 语义允许对存在控制依赖的操作做重排序的原因);但在多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。

3.2 volatile关键字

3.2.1 volatile作用

volatile字面意思是易变的、不稳定的。其作用是告诉虚拟机该变量是极有可能多变的,此处会禁止一些优化措施,不能随意变动目标指令,即禁止指令重排。此外,每一次读取volatile变量应该去共享内存中读取。因此,volatile能够保证有序性和可见性。

3.2.2 为什么说volatile无法保证原子性

简单的说,修改volatile变量分为四步:

  1. 读取volatile变量到本地缓存
  2. 修改变量值
  3. 本地缓存值刷写回共享内存
  4. 插入内存屏障,即lock指令,让其他线程可见。插入lock指令,会进行锁缓存,也就是会广播让其他线程的缓存失效,所有线程如果要重新读取变量需要重新到共享内存去读取。

因此,volatile通过插入内存屏障来保证可见性,通过禁止指令重排保证有序性,但是不能保证原子性!在高并发场景下,因为插入内存屏障前的操作并不是原子性的,以i++为例,如果线程1执行完i++之后还没来得及插入内存屏障刷写回主内存就被挂起,此时线程2读取完i进行i++,此时再挂起轮到线程1刷写回主内存,就会发生线程安全的问题了。

3.3 对比synchronized和volatile

synchronized关键字和volatile关键字是两个互补的存在:

  • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量,而synchronized关键字修饰的是方法以及代码块。
  • volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。

3.4 再看双重校验锁

public class Singleton {private static volatile Singleton singleton = null;private Singleton() {    }public static Singleton getInstance() {// 如果第一次检查instance不为null,那么就不需要执行下面的加锁和初始化操作。因此,可以大幅降低synchronized带来的性能开销。if (singleton == null) {		synchronized (Singleton.class) {// 抢不到锁的线程会进入锁对象所对应的monitor的EntryList列表中,当第一个线程执行完释放锁之后,其他阻塞线程获取到锁之后就会创建新的对象,所以得进行二次判空if (singleton == null) {		singleton = new Singleton();}}}return singleton;}}

采用volatile关键字修饰也是很有必要的,singleton = new Singleton(); 这段代码其实是分为三步:

  • 为singleton分配内存空间
  • 初始化singleton
  • 将singleton指向分配的内存地址

由于JVM具有指令重排的特性,执行顺序有可能变成1 -> 3 -> 2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程T1执行了1和3,此时T2调用getInstance()后发现singleton不为空,因此返回singleton,但此时singleton还未被初始化。使用volatile可以禁止JVM的指令重排,保证在多线程环境下也能正常运行。

4 ReentrantLock

4.1 可重入锁

可重入锁:是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁。

public static void main(String[] args) {new Thread(() -> {lock.lock();System.out.println("第1次获取lock:" + Thread.currentThread().getName());try {lock.lock();System.out.println("第2次获取lock:" + Thread.currentThread().getName());try {System.out.println(Thread.currentThread().getName());} finally {lock.unlock();}} finally {// 加锁几次就要解锁几次lock.unlock();}}).start();new Thread(() -> {lock.lock();try {System.out.println("第1次获取lock:" + Thread.currentThread().getName());} finally {lock.unlock();}}).start();
}

4.2 对比synchronized和ReentrantLock

synchronized是依赖于JVM实现的,并没有直接将API暴露给我们。ReentrantLock是API层面实现的,需要lock()和unlock()方法来完成。相比synchronized,ReentrantLock增加了一些高级功能:

  • 等待可中断: ReentrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
  • 可实现公平锁: ReentrantLock可以是公平锁,也可以是非公平锁,默认是非公平的。synchronized只能是非公平锁,ReentrantLock默认情况也是非公平的。

5 锁模型总结

5.1 公平锁和非公平锁

  • 公平锁:多个线程按序来获取锁;
  • 非公平锁:后申请锁的线程可能比先申请锁的线程更先获取到锁,可能会造成线程饥饿现象;
  • 具体实现:ReentrantLock可通过构造函数指定是否是公平锁,默认是非公平锁。而synchronized只能是非公平锁。

5.2 独享锁和共享锁

  • 独享锁(排他锁):每次只能有一个线程能持有锁;
  • 共享锁:允许多个线程同时获取锁,并发访问共享资源;
  • 具体实现:ReentrantLock是独享锁。ReadWriteLock中,读锁是共享锁,写锁是独享锁。synchronized方法是独享锁。

5.3 乐观锁和悲观锁

  • 乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁。只是在更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于多读的应用类型,这样可以提高吞吐量。乐观锁一般会使用版本号机制或者CAS算法实现。

    • 版本号机制:通过给共享资源加上一个版本号,表示数据被修改的次数,当数据被修改时,版本号会加1。当线程A要更新数据值时,在读取数据的同时也会读取版本号值,在提交更新时,若刚才读取到的版本号值与当前共享资源的版本号值相等时才更新,否则重试更新操作,直到更新成功。
    • CAS算法:需要读写的内存值V,进行比较的值A,拟写入的新值B。当且仅当内存V上的值等于A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作。
  • 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

  • 具体实现:乐观锁适用于多读场景,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。对应的,悲观锁适用于多写场景。JUC的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。synchronized和 ReentrantLock等独占锁就是悲观锁思想的实现。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows USB 设备驱动开发-编写 UCSI 客户端驱动程序

编写 UCSI 客户端驱动程序 USB Type-C 连接or 系统软件接口(UCSI)驱动程序充当带有嵌入式控制器(EC)的 USB Type-C 系统的控制器驱动程序。 如果实现平台策略管理器(PPM)的系统,如 UCSI 规范中…

国产化低功耗HDMI转VGA方案,大量出货产品,广泛应用在显示器以及广告机产品

芯片描述: 兼具高性能和低成本效益的优点,是一款可以将高清视频 HDMI1.4 数字信号转换成 VGA 模拟信号输出的芯片。不需要提供外部电源,ICNM7301 就可以在正常模式下使用;ICNM7301 广 泛适用于各种市场系统和显示应用体系&#x…

LabVIEW异步和同步通信详细分析及比较

1. 基本原理 异步通信: 原理:异步通信(Asynchronous Communication)是一种数据传输方式,其中数据发送和接收操作在独立的时间进行,不需要在特定时刻对齐。发送方在任何时刻可以发送数据,而接收…

2024年广东省安全员B证第四批(项目负责人)证模拟考试题库及广东省安全员B证第四批(项目负责人)理论考试试题

题库来源:安全生产模拟考试一点通公众号小程序 2024年广东省安全员B证第四批(项目负责人)证模拟考试题库及广东省安全员B证第四批(项目负责人)理论考试试题是由安全生产模拟考试一点通提供,广东省安全员B证…

手持式气象站:便携科技,掌握微观气象的利器

手持式气象站,顾名思义,是一种可以随身携带的气象监测设备。它小巧轻便,通常配备有温度、湿度、风速、风向、气压等多种传感器,能够实时测量并显示各种气象参数。不仅如此,它还具有数据存储、数据传输、远程控制等多种…

kafka开启kerberos和ACL

作者:恩慈 一、部署kafka-KB包 1.上传软件包 依次点击 部署中心----部署组件----上传软件包 选择需要升级的kafka版本并点击确定 2.部署kafka 依次点击部署中心----部署组件----物理/虚拟机部署----选择集群----下一步 选择手动部署-…

MongoDB自学笔记(四)

一、前文回顾 上一篇文章中我们学习了MongoDB中的更新方法&#xff0c;也学了一部分操作符。今天我们将学习最后一个操作“删除”。 二、删除 原始数据如下&#xff1a; 1、deleteOne 语法&#xff1a;db.collection.deleteOne(< query >,< options >) 具体参…

学生信息管理系统-可视化-科目管理CRUD代码生成器

学生管理系统中的科目管理是一个重要的组成部分&#xff0c;它负责维护和管理学校中所有的教学科目信息。 可视化快速界面生成CRUD界面&#xff0c;API通过代码生成器生成器生成。 新增数据库表 拷贝demo_table修改为clazz_kemu表 修改表结构 其中包括一个自增ID字段&#x…

在虚拟机 CentOS7 环境下安装 MySQL5.7 数据库

配置目标 在虚拟机的 Linux CentOS7 环境下安装 MySQL5.7 版数据库&#xff0c;并能从宿主机 Windows 系统连接该数据库&#xff08;默认端口&#xff1a;3306&#xff09;。 1. 准备工作 WMware 虚拟机&#xff1a;VMware Workstation 16 ProCentOS7 镜像&#xff1a;CentO…

Java面试题--JVM大厂篇之深入解析JVM中的Serial GC:工作原理与代际区别

目录 引言&#xff1a; 正文&#xff1a; 一、Serial GC工作原理 年轻代垃圾回收&#xff08;Minor GC&#xff09;&#xff1a; 老年代垃圾回收&#xff08;Major GC或Full GC&#xff09;&#xff1a; 二、年轻代和老年代的区别 年轻代&#xff08;Young Generation&a…

redis其他类型和配置文件

很多博客只讲了五大基本类型&#xff0c;确实&#xff0c;是最常用的&#xff0c;而且百分之九十的程序员对于Redis只限于了解String这种最常用的。但是我个人认为&#xff0c;既然Redis官方提供了其他的数据类型&#xff0c;肯定是有相应的考量的&#xff0c;在某些特殊的业务…

【C++】——new和delete

文章目录 热身试题C中的内存管理new与delete对于内置类型的操作new与delete对于自定义类型的操作 malloc/free和new/delete的区别 热身试题 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] { 1, 2, 3…

嵌入式物联网在教育行业的应用——案例分析

作者主页: 知孤云出岫 嵌入式物联网在教育行业的应用——案例分析 目录 作者主页:嵌入式物联网在教育行业的应用——案例分析一、引言二、智能教室&#xff1a;环境监测系统1. 硬件需求2. 电路连接3. 代码实现 三、个性化学习&#xff1a;智能学习平台1. 数据处理与分析2. 代…

Flutter中GetX的用法(超详细使用指南之路由依赖管理篇)

目录 1.前言 2.GetX 依赖管理概述 1.GetX 依赖管理的基本概念 2.与其他依赖管理工具的比较 3. 基础依赖注入 1.Get.put 2.Get.lazyPut 3.Get.putAsync 4.高级依赖注入 1.使用Get.create 2.依赖生命周期管理 5. 参考资料 1.前言 今天这篇博客主要介绍Getx的三大功能…

ESP8266模块(2)

实例1 查看附近的WiFi 步骤1&#xff1a;进入AT指令模式 使用USB转串口适配器将ESP8266模块连接到电脑。打开串口终端软件&#xff0c;并设置正确的串口和波特率&#xff08;通常为115200&#xff09;。输入以下命令并按回车确认&#xff1a; AT如果模块响应OK&#xff0c;…

R语言包AMORE安装报错问题以及RStudio与Rtools环境配置

在使用R语言进行AMORE安装时会遇到报错,这时候需要采用解决办法: AMORE包安装,需要离线官网下载安装包: Index of /src/contrib/Archive/AMORE (r-project.org)https://cran.r-project.org/src/contrib/Archive/AMORE/ 一、出现的问题 最近开始学习R语言,安装了最新版…

Window中 Redis下载安装

Redis7.2.3连接&#xff1a; 我用夸克网盘分享了「redis-windows-7.2.3.zip」&#xff0c;点击链接即可保存。打开「夸克APP」&#xff0c;无需下载在线播放视频&#xff0c;畅享原画5倍速&#xff0c;支持电视投屏。 链接&#xff1a;https://pan.quark.cn/s/4dfb0497707a 在安…

义务外贸wordpress独立站主题

健身器材wordpress网站模板 跑步机、椭圆机、划船机、动感单车、健身车、深蹲架、龙门架、健身器材wordpress网站模板。 https://www.jianzhanpress.com/?p4251 农业机械wordpress网站模板 植保机械、畜牧养殖机械、农机配件、土壤耕整机械、农业机械wordpress网站模板。 …

WebRTC音视频-前言介绍

目录 效果预期 1&#xff1a;WebRTC相关简介 1.1&#xff1a;WebRTC和RTC 1.2&#xff1a;WebRTC前景和应用 2&#xff1a;WebRTC通话原理 2.1&#xff1a;媒体协商 2.2&#xff1a;网络协商 2.3&#xff1a;信令服务器 效果预期 1&#xff1a;WebRTC相关简介 1.1&…

Windows FFmpeg 开发环境搭建

FFmpeg 开发环境搭建 FFmpeg命令行环境搭建使用FFmpeg官方编译的库Windows编译FFmpeg1. 下载[msys2](https://www.msys2.org/#installation)2. 安装完成之后,将安装⽬录下的msys2_shell.cmd中注释掉的 rem set3. 修改pacman 镜像源并安装依赖4. 下载并编译源码 FFmpeg命令行环境…