Java内存模型
- 《Java并发编程的艺术》第3章的标题《Java内存模型》,初一看自己还以为讲解的JVM的内存模型(堆、栈、方法区等)
- 真正学习时,发现这一章的内容组织对自己来说比较难理解,学得迷迷糊糊的
- 查看了一些资料,起码比不看的效果更好:
- 自己之前的博客:Java高并发之JMM(java内存模型、volatile变量、JMM的三大特性)
- 短小精悍的Java学习笔记:Java并发 —— 十、Java 内存模型
- 发现《Java并发编程的艺术》第3章,就是在详细介绍Java并发编程的三大特性中的两个特性:可见性、有序性
- 可见性
- 可见性指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改。
- Java 内存模型(简写JMM)是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性的
- 可见性的三种主要实现方式:volatile、 synchronized、final
- 主要有三种实现可见性的方式:
- 有序性
- JMM中,为了提高程序的执行性能,允许编译器和处理器对指令序列重新进行排序
- 这使得,本线程内观察,所有操作都是有序的;在其他线程观察当前线程,所有操作是无序的
- 不太理解 😂,重点在指令重排序: 指令重排序不影响单线程程序的执行结果,但可能会影响多线程程序的执行结果
- 重排序的前提:不会影响单线程程序的执行结果
1. 内存可见性
1.1 并发编程的2个关键问题:通信与同步
通信
- 所谓通信,是指线程间以何种机制来传递信息
- 线程的间的两种通信机制:共享内存、消息传递
- 共享内存:通过读写共享内存,进行隐式通信
- 消息传递:线程之间通过消息的发送与接收,进行显式通信
同步
- 所谓同步,是指程序中用于控制不同线程间操作发生相对顺序的机制
- 共享内存的通信机制中,程序员需要显式控制内存的互斥访问,是一种显式同步
- 消息传递的通信机制中,消息的发送必定早于消息的接收,是一种隐式同步
1.2 JMM的抽象结构
缓存一致性问题
- 操作系统中,为了寄存器和内存之间读写速度几个数量级的差异,引入了高速缓存
- 每个处理器都有自己的高速缓存,如果缓存同一块内存区域,这些高速缓存中的数据可能不一致
JMM的抽象结构
- 线程间的共享变量存储在主内存中,每个线程都有一个自己的工作内存,又叫本地内存
- 工作内存中存储了线程需要读/写的共享变量的副本
- 线程只能操作工作内存中共享变量的副本,不同线程间共享变量的同步依靠主内存完成
- 一些说明:
- 共享变量是指存储在堆内存中的变量,包括实例变量、静态变量、数组等
- 堆是线程共享的,而存储局部变量、操作数栈等的虚拟机栈是线程私有的
1.3 内存可见性的重要性
理想的线程通信:
- 线程A将本地内存中更新过的共享变量x的值写回主内存
- 线程B从主内存读取线程A已经更新过的共享变量的值
- 也就是说,线程读到的总是共享变量的最新值,而非本地内存中缓存的旧值
实际的线程通信
-
考虑这样的场景:初始时,共享变量
a = b = 0
时间 线程A 线程B 备注 t1 a = 1; // A1
b = 2; // B1
线程A将主内存中 a
的值更新为1,线程B将主内存中b
的值更新为2t2 x = b; // A2
y = a; // B2
线程A从主内存获取 b
的最新值,x
的值更新为2;
线程B从主内存获取a
的最新值,y
的值更新为1 -
上面的操作结果是处理器视角,它认为 A1 →\rightarrow→ A2(A1先于A2执行)
-
由于本地内存的存在,其实际执行顺序:A2 →\rightarrow→ A1
- A1操作的是本地内存,只有最后执行A3后,主内存中
a
的值才被更新为1 - 因此,对内存来说,是先读取主内存中尚未更新的
b
,然后再更新a
的值
- A1操作的是本地内存,只有最后执行A3后,主内存中
总结:本地内存影响了共享变量的可见性
- 由于本地内存的存在,导致共享变量的值在本地内存发生更新后,其他线程无法感知这个修改。
- 只有将修改后的共享变量的新值立即写回主内存,并且其他线程访问共享变量时,都从主内存而非本地内存获取最值,才能保证共享变量的
"可见性"
:一旦修改,立马可感知
1.4 理想的内存模型 —— 顺序一致性内存模型
- 顺序一致性内存模型是一个理论参考模型,JMM和处理器内存模型的设计都以该模型作为参考
- 顺序一致性内存模型具有两大特性:
- 一个线程中的所有操作必须按照程序的顺序来执行(不允许重排序)
- 不管程序是否正确同步,所有线程都只能看到一个单一的执行顺序。每个操作都必须原子执行,且执行结果立即对其他线程可见
- 对特性二的理解:
- 可以看做一个单开关多灯模型,开关可以随意switch到某个灯,使其点亮
- 也就是说,任意时刻只能有一个线程连接到内存,从而对内存进行读/写操作
- 这样,多线程的并发执行被这个唯一的开关变成了串行,使得多线程中所有的操作具有全序
注意:64 bit变量的写操作不具有原子性
- 32 bit的处理器,单个写操作原本是具有原子性的
- 64 bit的处理器,对long或double类型变量的写操作将由两个写操作实现。
- 此时,64 bit变量的写操作将不再具有原子性
2. 重排序
- 为了提高程序的执行性能,编译器和处理器往往会对指令序列进行重排序
2.1 重排序的分类
3种类型的重排序
-
编译器优化重排序:
- 编译器在不改变单线程程序语义的情况下,可以重新安排语句的执行顺序
- 例如,针对不同变量的写操作:
// 原始顺序 a = 10; flag = true; // 重排序后的顺序 flag = true; a = 10;
-
指令级并行重排序:
- 现代处理器支持指令级并行(ILP)技术,可以将多条指令重叠执行
- 若不存在数据依赖性,处理器可以改变语句对应的机器指令的执行顺序
-
内存系统重排序 (并未真正的重排序?)
- 处理器使用缓存和读/写缓冲区,使得load和store操作看起来可能是乱序执行的
- 处理器使用缓存和读/写缓冲区,使得load和store操作看起来可能是乱序执行的
-
其中,指令级并行重排序和内存系统重排序统称为处理器重排序
-
常见处理器所允许的重排序规则
- 常见的处理器都不允许对存在数据依赖的操作进行重排序
- 常见的处理器都允许对
Store - Load
操作进行重排序 - 从上到下,这些处理器允许的重排序规则逐渐增加,这是为了降低内存模型的束缚以追求更高的性能
禁止重排序
- 重排序可能会导致多线程之间出现内存可见性问题,有时需要禁止重排序
- 对于编译器重排序,JMM的编译器重排序规则会禁止特定类型的编译器重排序
- 对于处理器重排序,JMM的处理器重排序规则会在编译器生成指令序列时,插入特定类型的内存屏障,从而禁止特定类型的处理器重排序
2.2 内存屏障
-
内存屏障,是一组机器指令,用于实现对内存操作的顺序限制
-
自己的理解:可以限制内存读/写操作先后顺序的一组机器指令
-
共有4种内存屏障:对内存的操作只有两种 (读/写),内存屏障是针对这两种操作的组合
屏障类型 指令示例 说明 LoadLoad load1; LoadLoad; load2;
load2及其后续的load操作执行前,load1中的load操作已全部完成 StoreStore store1; Store; store2;
store2及其后续的store操作执行前,store1中的store操作已全部执行完成
也就是store1中的操作的数据要先对其他处理器可见LoadStore load1; LoadStore; store2;
store2及其后续store操作执行前,load1中的load操作已全部执行完 StoreLoad store1; StoreLoad; load2;
load2及其后续load操作执行前,store1中的store操作已全部执行完 -
StoreLoad是开销最昂贵的内存屏障,也是全能型的内存屏障,同时具有其他3种内存屏障的效果
- StoreLoad屏障要求该屏障之前的所有内存访问指令(包括store和load)完成之后,才会执行该屏障之后的内存访问指令
- 开销昂贵的原因:需要将写缓冲区中的数据全部刷新到内存中
絮絮叨叨
- 其实,自己对内存屏障的理解也不是很透彻
- 只知道:
- ① 内存屏障可以对内存访问的顺序进行限制,进而可以禁止指令重排序
- ② 根据读写操作的组合,有四种内存屏障;
- ③ StoreLoad的开销最为昂贵且兼备其他三种内存屏障的效果
补充知识:
- 后续的学习中,可以了解到:
- 内存屏障可以进行优化,去除不必要的内存屏障,提高程序执行效率
- 甚至在某些处理器中(x86),可以不使用某些内存屏障
2.3 数据依赖 & as-if-serial语义 (重排序对单线程的影响)
数据依赖
- 两个操作访问同一个变量,且其中一个操作为写操作。此时,这两个操作之间存在数据依赖
- 单个处理器或单个线程中,上面的三种情况,两个操作的执行顺序一旦改变,程序执行结果将会改变。
- 编译器和处理器(大多数)不会对存在数据依赖的操作重排序
as-if-serial语义
as-if-serial
语义的规定:不管怎么重排序,单线程程序的执行结果不能被改变- 编译器、runtime和处理器,都需要遵守 as-if-serial 语义
- as-if-serial 语义为单线程执行制造了一种假象:单线程程序是按照代码顺序依次执行的,但实际可能发生了重排序,只是因为执行结果未改变,程序员难以感知
- 例如,计算圆面积的代码如下:数据依赖关系为 A →\rightarrow→ C,B →\rightarrow→ C,即操作A和操作B之间不存在数据依赖
- 因此,可以重排序为 B →\rightarrow→ A →\rightarrow→ C,重排序后的执行结果不变
2.4 重排序对多线程的影响
- 从上面的讲解可知:单线程下,不存在数据依赖的操作可以重排序,而程序的执行结果不会被改变
- 但在多线程下,即使不存在数据依赖,重排序也可能会影响程序的执行结果
- 示例代码如下:
- 操作1和操作2不存在数据依赖,可以重排序
- 重排序后,多线程访问将存在问题:(带箭头的虚线标识错误的读操作,带箭头的实线标识正确的读操作)
- 线程B访问flag时,获取到的值为
true
,条件判断为真。 - 随后,线程B读取变量a的值,为初始化时的
0
并非预期的1
- 线程B访问flag时,获取到的值为
3. happens-before规则
- happens-before规则,又叫先行发生原则,是JMM的的核心概念
- 从JDK 5开始,Java使用JSR-133内存模型,该内存模型使用happens-before规则来阐述操作之间的内存可见性
3.1 happens-before的定义
设计者的考虑
- JMM的设计者,在设计JMM时,应该考虑下面两个因素:
- 一方面,要为程序员提供足够强的内存可见性保证
- 另一方面,对编译器和处理器的限制要尽可能的放松(束缚过多,没有足够提高性能的优化空间)
- JMM实质上遵循一个原则:在单线程和正确同步的多线程程序中,只要不改变程序的执行结果,编译器和处理器怎么优化都行
- 程序执行过程中,锁只会被一个线程访问,则锁可以被消除
- volatile变量只会被一个线程访问,则可以将其当做一个普通变量处理
happens-before的定义
- 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二操作可见,且第一个操作的执行顺序排在第二个操作之前
- 两个操作之间存在happens-before关系,并不意味着Java必须按照happens-before关系指定的顺序来执行。因为若重排序后的执行结果,与按照happens-before的执行结果一致,那允许进行重排序
说明
- 这两个定义看起来相互矛盾,定义1已经规定了两个操作执行的先后顺序,定义2又说允许重排序
- 定义1是JMM对程序员的承诺:A happens-before B,从程序员视角来看,A的执行结果对B可见,且A的执行顺序在B之前
- 定义2是JMM对编译器和处理器重排序的约束:
- 重排序的前提是,执行结果不能被改变。
- 对程序员来说,他更关心的是执行结果而非执行过程
- happens-before语义和as-if-serial语义本质相同:
- 前者保证,正确同步的多线程程序执行结果不被改变;后者保证,单线程程序的执行结果不被改变
- 前者让程序员误认为:正确同步的多线程程序是按照happens-before指定的顺序执行的
- 后者让程序员误认为:单线程程序是按照程序的顺序来执行的
3.2 happens-before规则
程序顺序规则
- 又叫单一线程原则:在一个线程内,程序前面的操作,happens-before程序后面的操作
监视器锁规则
- 对一个锁的解锁操作,happens before 对这个锁的加锁操作
volatile变量规则
- 对一个volatile变量的写操作,happens before对这个volatile变量的读
start()规则
- 线程A执行threadB.start()操作启动线程B,则线程A的threadB.start()操作happens before 线程B中的任意操作
- 很好理解:人家不启动你,你怎么可能执行自身操作
join()规则
- 线程A执行threadB.join()并成功返回,则线程B中任意操作 happens before 线程A从threadB.join()操作成功返回
线程中断规则
- 执行thread. interrupt() 操作,happens before被中断线程检测到中断事件的发生
- 可以通过 interrupted() 或 isInterrupted() 方法,检测线程是否被中断
对象终结规则
- 对象的初始化的完成,happens before 该对象的 finalize() 方法的开始
传递性规则
- 如果A happens-before B,B happens-before C,则 A happens-before C
- 这对理解happens-before原则如何提供跨线程的内存可见性非常重要
4. 基于内存语义,了解可见性保证
- happens-before,是JMM在内存可见性问题上对程序员的承诺
- 如何实现这个可见性承诺,需要依靠具体的内存语义来实现
4.1 volatile
4.1.1 volatile的内存语义
- volatile变量的代码示例如下
volatile内存语义
- 线程A写volatile变量时,JMM会把线程A对应的本地内存中的共享变量刷新到主内存
- 从通信的角度看,是线程A向后续读volatile变量的线程发出了消息,告知它们共享变量已被修改
- 线程B读volatile变量时,JMM会把线程B对应的本地内存置为无效;线程B需要从主内存读取共享变量,这时读取到的是共享变量的最新值
- 从通信的角度看,是线程B接收到了向线程A发出的消息,知道了共享变量已被修改的事情
- 整体来看,线程A通过主内存向线程B发送消息
4.1.2 volatile内存语义的实现
- volatile重排序规则如下
- 第三行最后一个单元格为No,表示当第一个操作为普通读/写操作,第二个操作为volatile写操作时,编译器不能重排序这两个操作
- 第三行最后一个单元格为No,表示当第一个操作为普通读/写操作,第二个操作为volatile写操作时,编译器不能重排序这两个操作
volatile写内存语义
- 写操作之前插入一个
StoreStore
内存屏障,禁止前面的普通写与后面的volatile写重排序- 这样可以保证volatile写被刷新回主内存之前,前面的普通写已经刷新到内存
- 这也是为什么,上面的写内存语义中说:将线程对应的本地内存中的共享变量刷新到主内存,而非:将线程对应的本地内存中的volatile变量刷新到主内存
- 写操作之后插入一个
StoreLoad
内存屏障(全能型的),禁止前面的volatile写重排序与后面可能的volatile读/写重排序- 编译器无法判断是否需要在volatile写后面插入内存屏障,JMM采取保守策略:在volatile变量写之后或读之前插入一个
StoreLoad
内存屏障 - 考虑到多读少写的场景更多,最终在volatile变量写之后插入一个
StoreLoad
内存屏障,以提升程序的执行效率
- 编译器无法判断是否需要在volatile写后面插入内存屏障,JMM采取保守策略:在volatile变量写之后或读之前插入一个
volatile读内存语义
- 不是很理解读内存语义,欢迎交流
- 读操作之后,先插入一个
LoadLoad
内存屏障,禁止前面的volatile读操作与后面的普通读操作重排序 - 读操作之后,再插入一个
LoadStore
内存屏障,禁止前面的volatile读操作与后面的普通写操作重排序
内存语义的优化
-
上面的读写内存语义是保守的,具体的代码中还可以对生成的指令序列进行优化,减少不必要的内存屏障
-
优化后的指令序列如下
-
如果是x86处理器,volatile读写操作的指令序列还可以简化如下
- x86处理器只允许写 - 读操作重排序,可以直接省略读- 写、读 - 读, 写 - 写操作对应的内存屏障
- x86处理器只允许写 - 读操作重排序,可以直接省略读- 写、读 - 读, 写 - 写操作对应的内存屏障
4.1.3 JSR-133对volatile内存语义的增强
-
JSR-133之前的旧JMM中,不允许volatile变量之间的重排序,但允许volatile变量与普通变量的重排序
-
这样的内存模型将存在一定问题:结合volatile的示例代码:
- 由于不存在数据依赖,volatile写操作前的普通变量写操作可能被置后
-
线程A执行writer()方法后,线程B执行reader()方法
- 线程B在执行操作4时,无法看到线程A对普通变量的修改
- 但按照程序员的期望,既然都能看到volatile变量的修改,那之前的普通变量的修改也应该可见 😂
-
增强之后的volatile内存语义:
- 根据volatile写操作前的
StoreStore
内存屏障,我们可知:writer()方法中,对共享变量i
的写操作不会被重排序 - 根据volatile读操作后的
LoadLoad
内存屏障,我们可知:reader()方法中,对共享变量i
的读操作不会被重排序
- 根据volatile写操作前的
-
线程A执行writer()方法后,线程B执行reader()方法,执行顺序如下
- 通过程序顺序规则,1 happens before 2, 3 happens before 4
- 根据volatile规则,2 happens before 3
- 根据传递性规则, 1 happens before 4
4.2 锁
4.2.1 锁的内存语义
-
基于synchronized锁的代码如下:假设线程A执行writer()方法,更新共享变量;随后,线程B执行reader()方法
-
线程A释放锁时,JMM会把线程A对应的本地内存中的共享变量刷新到主内存
- 从通信的角度看:线程A向后续的线程发出消息:共享变量已经被修改
-
线程B获取锁时,JMM会把线程B对应的本地内存置为无效;从而,执行reader()方法时,需要从主内存获取共享变量的值
- 从通信的角度看:线程B接收到了线程A修改共享变量的消息
-
线程A释放锁,随后线程B获取锁,实际是线程A通过主内存向线程B发送消息
-
总结:
- 获取锁和释放锁,还隐藏了更新主内存中共享变量的值、获取主内存中共享变量的值
- 对比volatile的内存语义,可知:锁的释放对应volatile变量的写操作,锁的获取对应volatile变量的读操作
示例代码中,锁的happens-before关系
- 根据程序顺序规则, 1 happens before 2, 2 happens before 3, 4 happens before 5,5 happens before 6
- 根据监视器锁规则, 3 happens before 4
- 根据传递性,2 happens before 5
4.2.2 锁内存语义的实现
- 本小节将基于可重入锁ReentrantLock了解锁内存语义的实现
- ReentrantLock基于AQS(AbstractQueuedSynchronizer,同步器)实现,AQS中有一个int类型的volatile变量来维护同步状态
- volatile变量是ReentrantLock内存语义实现的关键
公平锁的获取
-
使用公平锁时,加锁方法lock()的调用轨迹如下
1. ReentrantLock: lock() 2. FairSync: lock() 3. AbstractQueuedSynchronizer: acquire(1) 4. FairSync: tryAcquire(1)
-
第4步,
tryAcquire(int acquires)
是真正实现加锁的方法- 获取锁前,首先读取volatile变量的值
protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState(); // 获取锁时,先读取volatile变量stateif (c == 0) { // 成功获取锁,将当前线程设置为锁的拥有者if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) { // 通过查收将state更新为1setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) { // 当前线程重复获取锁int nextc = c + acquires; // state加1if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc); // 直接更新state的值return true;}return false;} }
公平锁的释放
-
使用公平锁时,解锁方法unlock()的调用轨迹如下
1. ReentrantLock: unlock() 2. AbstractQueuedSynchronizer: release(1) 3. Sync: tryRelease(1)
-
第3步,
tryRelease(int releases)
是实现释放锁的实际方法protected final boolean tryRelease(int releases) {int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c); // 释放锁的最后,写volatile变量statereturn free; }
-
公平锁,释放锁的最写volatile变量state,获取锁时首先度volatile变量state的值
-
根据volatile变量的happens-before规则,一个线程释放锁后,后续线程获取同一个锁,锁计数器的值立即可见
-
非公平锁的释放,也是一样的调用轨迹
非公平锁的获取
-
非公平锁的释放轨迹如下:
1. ReentrantLock: lock() 2. NonfairSync: lock() 3. AbstractQueuedSynchronizer: compareAndSetState(int expect, int update) 4. 或AbstractQueuedSynchronizer: acquire(int arg) ---> NoFairSync: tryAcquire(int acquires)
-
关键代码如下
- 不管是否是首次获取,最终都将调用AbstractQueuedSynchronizer的compareAndSetState(int expect, int update)方法,原子更新state变量的值
final void lock() {if (compareAndSetState(0, 1)) // 首次获取锁setExclusiveOwnerThread(Thread.currentThread());else // 重复获取锁acquire(1); }protected final boolean compareAndSetState(int expect, int update) {// See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
-
CAS操作需要先读volatile变量的值,如果符合预期,再写volatile变量的值
-
CAS操作将同时具有volatile读和volatile写的内存语义
- volatile读:编译器不会将volatile读与volatile读之后的任意内存操作重排序
- volatile写:编译器不会将volatile写与volatile写之前的任意内存操作重排序
- 综合起来,编译器不会将CAS与CAS前后的任意内存操作重排序
- 从而保证非公平锁释放后、再获取,volatile变量state是内存可见的
总结
- 公平锁或非公平锁的释放,最后都需要写volatile变量state
- 公平锁获取,首先需要读volatile变量state;非公平锁的获取,使用CAS更新volatile变量state的值。
- 由于CAS同时具有volatile读和写的内存语义,获取锁时volatile变量state是内存可见的
4.2.3 concurrent包的实现
-
Java concurrent包的通用实现方式;
- 将状态变量定义为volatile共享变量
- 借助volatile的读写内存语义、CAS同时具有volatile读写内存语义的特性,实现线程间的通信:对状态变量的修改是内存可见的
- 同时,借助CAS解决多线程竞争锁的问题,从而实现线程间的同步
-
AQS、非阻塞数据结构、原子变量类,这些concurrent包中的基础类,几乎都使用这种模式实现的
-
同时,concurrent包的高层类又是基于这些基础类实现的。
-
可以说,上述实现方式在concurrent包中是通用的
4.3 final
4.3.1 final域的重排序规则
- 写final域的重排序规则:
- 在构造函数内对一个final域的写入,与随后把被构造对象赋值给一个引用变量,这两个操作之间不能重排序
- 也就是说,对final域的写入不能被重排序到构造函数之外
- 读final域的重排序规则:
- 初次读包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序
- 也就是说,初次读final域不能被重排序到初次读对象的引用之前
写final域的重排序规则
- JMM禁止编译器把final域的写重排序到构造函数之外
- 编译器会在写final域、构造函数返回之前,插入一个StoreStore内存屏障,从而禁止处理器把final域的写重排序到构造函数之外
- 代码示例如下
- 线程A执行writer()方法,随后线程B执行reader()
- 写普通域操作被编译器重排序到了构造函数之外,线程B读到是普通域未初始化的值
- 写final域的重排序规则将写final域操作限定在构造函数中,线程B可以正确读取final域初始化的值
- 总结:
- 写final域的重排序规则确保,在对象引用为任意线程可见之前,对象的final域已经正确初始化
读final域的重排序规则
- 由于读包含final域对象的引用、读final域之间存在间接依赖关系,编译器是不会对这两个操作进行重排序的
- 大多数处理器也不会重排序,但少数处理器会对存在间接依赖关系的操作重排序
- 因此,读final域重排序规则针对的是处理器,编译器会在读final域操作前插入
LoadLoad
内存屏障以禁止重排序 - 仍然以上面的代码为例:
- 读普通域操作被重排序到读对象引用之前,读到的普通域未正确初始化
- 读final域被限定在读对象引用之后,读到的final域已经完成了初始化
- 总结:
- 读final域的重排序规则确保,读对象的final域之前,一定会先读该对象的引用
- 只要对象引用不为
null
(成功构造),则对象中的final域一定已经完成了初始化
自己的理解
- 写final域的重排序规则,使得构造函数返回前,对象中的final域已经成功初始化 —— 这是前提
- 读final的重排序规则,使得只要对象引用不为null,读到的对象final域一定成功初始化 —— 这是在该前提下的必然结果
x86处理器对final域重排序规则的支持
- x86处理器不会对写 - 写操作重排序,因此写final域无需添加StoreStore屏障
- x86处理器不会对存在间接依赖的操作重排序,因此读final域无需添加LoadLoad屏障
- 也就是说,x86处理器针对final与的重排序规则天生就支持,无需借助额外的内存屏障指令来实现
4.3.2 final引用不能从构造函数溢出
- 示例代码中,在构造函数返回前,被构造对象的引用可以被其他线程看见
- 被构造对象的引用发生逸出,对象中的final与可能尚未初始化
- 也就是说,final引用从构造函数逸出了,并未按照达到预期(构造函数返回前,final域已经初始化)
- 这使得线程B在访问对象引用时,对象引用虽然不为null,但是对象中的final域尚未初始化
4.3.3 JSR-133对final语义的增强
- 在旧的JMM中,一个线程看到的final域可能是尚未初始化,一段时间内再读final域发现值发生了变化
- 这样的现象根本不符合final域的定义:不可变量
- 增强后的final语义可以保证:只要final引用没有从构造函数中逸出,不需要使用同步就能保证任意线程看到的是final域初始化后的值
5. JMM在单例模式中的应用
5.1 不安全的DCL
-
单例模式的相关知识,可以参考之前的博客:层层递进,实现单例模式
-
针对最简单的饿汉模式,我们提出可以使用懒汉模式:在获取唯一实例时才进行创建
-
原始的懒汉模式是非线程安全的,于是使用
synchronized
修饰全局访问点 -
synchronized实现同步,却导致了性能下降,于是提出使用DCL实现懒汉模式
public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) { // 第一重检验,避免不必要的同步synchronized (Singleton.class) { // 同步锁if (instance == null) { // 第二重检验,避免重复创建instance = new Singleton();}}}return instance;} }
-
仔细分析发现,这样的实现也存在问题,主要是
instance = new Singleton()
对应多个操作
-
这些操作可能被JVM重排序如下
-
其他线程发现instance不为
null
并直接返回后,将会访问到一个尚未初始化的对象
-
这样的重排序没有违反
intra-thread semantics
,在没有改变单线程的执行结果的前提下,可以提高程序的执行性能 -
解决办法:
- 保证2和3不会被重排序,使得其他线程发现对象不为
null
时,对象已经初始化 - 允许2和3重排序,但不允许其他线程看到这个重排序。也就是说,初次访问对象一定是在整个
instance = new Singleton()
执行完之后
- 保证2和3不会被重排序,使得其他线程发现对象不为
lock前缀指令
-
在x86处理器下通过工具获取JIT编译器生成的汇编指令来看看对Volatile进行写操作CPU会做什么事情。
-
Java代码
instance = new Singleton();
,对应的汇编代码如下:关键在lock前缀指令0x01a3de1d: movb $0x0,0x1104800(%esi);0x01a3de24: lock addl $0x0,(%esp);
-
lock前缀指令相当于一个内存屏障,主要提供3个功能:
- 确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面
- 强制将处理器缓存回写到内存
- 如果是缓存回写操作,它会导致其他处理器的缓存行无效
-
这样的话,volatile不仅能禁止指令重排序,还能保证变量的内存可见性
-
参考链接:java内存屏障的原理与应用
5.2 volatile:禁止2和3重排序
- 将instance定义为volatile类型,就可以禁止2和3重排序
- 3是在写volatile变量,根据写volatile的内存语义,会在写操作前添加StoreStore屏障,从而禁止2重排序到3之后
- 这样就可以保证其他线程在判断对象不为
null
时,对象已经初始化
5.3 静态内部类
- 允许2和3重排序,但要保证执行初次访问对象时,2和3都已执行,即对象已成功构建
- 静态内部类刚好可以满足需求
- 静态内部类在第一次有线程访问
getInstance()
方法,执行return LazyHolder.instance;
时才会被加载 —— 满足延迟加载的需求 - instance作为静态内部类的静态成员,在内部类被加载时就已经完成初始化
- 一旦能访问到instance对象,instance对象已就绪
public class Singleton {private Singleton() {}private static class LazyHolder {private static Singleton instance = new Singleton();}public static Singleton getInstance() {return LazyHolder.instance;} }
- 静态内部类在第一次有线程访问
- Java语言规定,每个类或接口,都有一个唯一的初始化锁与之对应,从而类加载的整个过程是线程安全的
- 因此,在类加载中完成初始化的instance也是线程安全的
两种方案的对比
- 静态内部类的实现方案更加简洁,但volatile不仅支持静态变量的初始化,还支持示例变量的初始化
- 需要对实例字段实现线程安全的延迟初始化,可以使用volatile方案
- 需要对静态字段实现线程安全的延迟初始化,可以使用静态内部类方案
6. JMM总结
- JMM这一部分学习了好久,总结起来也挺费劲的
内存可见性
- 所谓内存可见性:一个线程对共享变量的修改,其他线程立即可感知
- 处理器中缓存的引入,带来的缓存一致性问题;JMM的抽象结构,以及本地内存带来的内存可见性问题(处理器和内存看到的执行顺序不一致)
- 理想化的内存模型:顺序一致性内存模型,保证单线程内按程序顺序执行,多线程能看到一个统一的全序、且执行结果立即可见(单开关多灯模型)
- Java程序的内存可见性保证:
- 单线程程序:不会出现内存可见性问题,在执行结果与在顺序一致性内存模型中的执行结果一致的前提下, 可以进行重排序
- 正确同步的多线程程序:执行结果与在顺序一致性内存模型中的执行结果一致,通过限制编译器和处理器的重排序来为程序员提供内存可见性
- 为同步/正确同步的多线程程序:JMM提供最小安全性保障,线程读取到的值,要么是之前线程写入的值,要么是默认值
重排序
- 重排序的分类:编译器重排序、处理器重排序(指令级并行重排序、内存系统重排序)
- 常见处理器允许的重排序:禁止对存在数据依赖的操作重排序,都允许写 - 读操作重排序,x86处理器只允许写 - 读操作重排序
- 如何禁止重排序:编译器重排序规则,编译器插入内存屏障禁止特定类型的处理器重排序
- 四种内存屏障及其含义,开销最昂贵、全能型的内存屏障
StoreLoad
- 操作同一个变量时,数据依赖的三种情况;
as-if-serial
语义,不管怎么重排序单线程程序的执行结果不能被改变
happens-before语义
- 定义:
- 对程序员的承诺:1 happens before 2,则1的操作结果对2内存可见,且1在2之前执行
- 放松对编译器和处理器的约束:在保证执行结果不变的前提下,允许不按照
happens-before
指定的顺序执行
- 与
as-if-serial
语义的对比理解:一个针对正确同步的多线程程序,一个针对单线程程序 - happens-before的几大规则:程序顺序规则、volatile规则、监视器锁规则、线程start规则、线程join规则、线程interrupt规则、对象finalize规则、传递性
volatile、锁、final的内存语义
- volatile
- 内存语义:写volatile,本地内存中的共享变量刷新回主内存;读volatile,本地内存失效,需要从主内存读最新值
- 内存语义的实现:写volatile操作,之前插入StoreStore屏障,之后插入StoreLoad屏障;读volatile操作,之后先插入LoadLoad屏障,再插入LoadStore屏障
- JSR-133对volatile内存语义的增强:禁止volatile变量与普通变量的重排序
- 锁
- 内存语义:释放锁,本地内存中的共享变量刷新回主内存;获取锁,本地内存失败,需要从主内存读最新值;与volatile内存语义的对比(二者具有对应关系)
- 内存语义的实现:
- 基于可重入锁ReentrantLock,关键在AQS中的volatile变量state
- 公平锁和非公平锁释放时,最后都需要写volatile变量state
- 公平锁获取时,首先读volatile变量state
- 非公平锁通过CAS获取锁,同时具有volatile变量读写操作的内存语义
- concurrent包的通用实现模式:
- 将状态变量定义为volatile
- 通过volatile变量读写内存语义、CAS同时具有volatile变量读写内存语义,实现线程通信
- CAS更新状态变量,保证竞争锁时的线程同步
- final
- final域重排序规则:
- 构造函数中,写final域操作不能重排序到构造函数之外。(写操作后,插入StoreStore屏障)
- 读final域之前,需要先读final域所在的对象引用。(读操作前,插入LoadLoad屏障)
- 前者保证对象引用被其他线程可见前,final域已经正确初始化(前提);后者保证,只要对象引用不为
null
,对象中的final域一定已初始化(前提的必然结果)
- x86处理器的特殊性,无需插入任何屏障就能保证final域重排序规则
- final引用从构造函数溢出,将无法保证对象引用不为
null
时,final域已初始化
- final域重排序规则:
JMM的在单例模式的应用
- 不安全的DCL存在的问题,如何解决这个问题:禁止对象初始化和引用赋值的重排序 ;允许重排序,但初次访问对象引用,不能提前
- 前者,使用volatile解决:lock前缀指令及其作用
- 后者,使用静态内部类解决:内部类的延迟加载、线程安全的初始化(初始化锁保证)
参考文档:
- 图片参考:谁给解释下java内存模型读volatile域时的语义?
- 重要内容总结(volatile、锁、单例):面试:为了进阿里,重新翻阅了Volatile与Synchronized
- happens-before和内存屏障的关系:漫画:volatile对指令重排的影响
- 从汇编看Volatile的内存屏障
- 短小精悍的Java学习笔记:Java并发 —— 十、Java 内存模型
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- ubuntu18.04.5安装x11vnc0.9.13
装包:apt install x11vnc 添加密码:x11vnc -storepasswd 查看vnc进程:ps -ef | grep vnc 启动vnc服务:x11vnc -display :0 -rfbport 5901 -rfbauth ~/.vnc/passwd -forever 可以写一个shell脚本 vi vnc.sh #!/bin/bash x11vnc -d…...
2024/5/3 2:29:43 - B+树的理解以及在mysql中的应用
AVL 树和红黑树这些二叉树结构的数据结构可以达到最高的查询效率这是毋庸置疑的。 既然如此,那么数据库索引为什么不用 AVL 树或者红黑树呢? 这就牵扯到一个问题了,不考虑每种数据结构的前提条件而选择数据结构都是在耍流氓。 AVL 数和红黑…...
2024/5/3 2:29:39 - 第7周作业实验---串口通信
一、USART简介 通用同步异步收发器(Universal Synchronous Asynchronous Receiver and Transmitter)是一个串行通信设备,可以灵活地与外部设备进行全双工数据交换。 串行通信一般是以帧格式传输数据,即是一帧一帧的传输,每帧包含有起始信号、…...
2024/5/4 15:02:06 - Pytorch基础操作 —— 11. 改变张量的维度(维度修改)
文章目录torch.reshape例程FunctionDescriptionDetailreshapeReturns a tensor with the same data and number of elements as input, but with the specified shape. torch.reshape 维度改变是很常见的一种操作,合理修改张量维度能帮我们省很多事,但是…...
2024/5/3 1:03:04 - sql注入绕过空格过滤
SQL注入时,空格的使用是非常普遍的。比如,我们使用union来取得目标数据: http://www.xxx.com/index.php?id1 and 0 union select null,null,null 上面的语句,在and两侧、union两侧、select的两侧,都需要空格。 1. 注释…...
2024/5/3 2:29:27 - 多媒体标签
1.——————a标签2.——————img标签3.——————多媒体标签1.a标签 a标签——————超链接标签 href属性———————跳转的地址 target属性 回到顶部: 2.img标签 图片的形式:JPG、PNG、GIF、BMP、SVG等 <img] src"图片的路径&quo…...
2024/5/3 2:29:23 - 在 CentOS 上安装 Visual Studio Code
先是试了以下方式行不通,说明软件源中没有连接信息。 [qkmbogon etc]$ sudo yum install code 上次元数据过期检查:1:00:50 前,执行于 2021年10月31日 星期日 13时44分17秒。 未找到匹配的参数: code 错误:没有任何匹配: code [q…...
2024/5/4 14:01:22 - [Pixhawk/PX4]开发环境搭建(Ubuntu 18.04)和问题总结
主要记录了PX4环境在Ubuntu 18.04下的搭建过程和问题总结。 由于gazebo在ros安装中一般会包括,所以gazebo安装没有介绍。 一、基础资源下载 这是我第一次接触ubuntu系统的安装,所以可能有些步骤有错误或者多余,敬请批评指正。 1.下载安装…...
2024/5/3 2:29:15 - preg_match_all用法
(PHP 4, PHP 5, PHP 7, PHP 8) preg_match_all — 执行一个全局正则表达式匹配 说明 preg_match_all( string $pattern, string $subject, array &$matches null, int $flags 0, int $offset 0 ): int|false|null 搜索subject中所有匹配pattern给…...
2024/5/2 19:14:39 - 018 通过链表学Rust之实现pop front
介绍 视频地址:https://www.bilibili.com/video/av78062009/ 相关源码:https://github.com/anonymousGiga/Rust-link-list 详细内容 pop front 上一节,我们给双链表实现了new和push front方法,本节我们实现pop frontÿ…...
2024/5/3 2:29:11 - MySQL 8 的学习——6在批处理模式下使用mysql
在前面的部分中,您以交互方式使用mysql输入语句并查看结果。您也可以在批处理模式下运行mysql。为此,将要运行的语句放在文件中,然后告诉 mysql从文件中读取其输入:shell> mysql < batch-file## 如果您在Windows下运行mysql并且文件中有一些特殊字符会导致问题,您可…...
2024/5/4 18:03:50 - 面试题:深克隆和浅克隆的实现方式
面试题:深克隆和浅克隆的实现方式 面试官考察点 考察目的:深克隆和浅克隆,考察的是Java基础知识的理解。 考察人群:2到5年开发经验。 背景知识详解 先了解下浅克隆和深克隆的定义: 浅克隆:被复制对象的…...
2024/5/3 2:29:13 - 第一章程序设计与c语言
int main 主函数 printf 输出 scanf 输入 1.2 .1 程序与程序设计语言 机械语言:难写、难记、难修改 汇编语言:可移植性不好 高级语言:易学、易用、易维护,更有效 1.非结构化的语言 2.结构化语言 3.面向对象的语…...
2024/5/3 2:29:01 - 【Notes18】一键盘导出log工具,insmod,/sys/bus/pci/devices/0000
文章目录1.auto_dump.sh2.auto_dump.cfg3.auto_dump.sh4.reload_fpga_uio_driver.sh5.get_fpga_temp.sh6.ethernet_port.sh7.ddr_test.sh1.auto_dump.sh #!/bin/bashwork_path$(pwd) conf_file_path"${work_path}/auto_dump.cfg" dst_dir_path"" search_p…...
2024/5/3 2:28:55 - Spring框架的环境搭建
Spring框架的环境搭建 新建Maven项目 1.创建Maven的普通java项目 2. 设置项目的坐标、名称、工作空间 3. 设置项目的 Maven 环境 设置项目环境 1.修改JDK版本 <properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.…...
2024/5/3 2:28:51 - 恨啊,网易架构师500页趣谈网络笔记遭助手泄露,GitHub人手一份
网络协议知识点太多,学完记不住。大部分读者应该都学过计算机网络课程,学的时候感觉并不难,尤其这门课没有公式,更像是文科的东西。学了一大堆东西,也背了一大堆东西,但是最终应付完考试之后,都…...
2024/5/3 2:28:48 - MyBatis配置
思路流程:搭建环境–>导入Mybatis—>编写代码—>测试 代码演示 1、搭建实验数据库 CREATE DATABASE mybatis; USE mybatis; DROP TABLE IF EXISTS user; CREATE TABLE user ( id int(20) NOT NULL, name varchar(30) DEFAULT NULL, pwd varchar(30) DEF…...
2024/5/3 2:28:44 - 第2章 数据类型、运算符和表达式
C程序是一个字符序列,字符序列先被分解为称之为记号(token)的词法元素,再根据语法规则检查这些记号组合是否合法。 C语言是一门大小写敏感的,抢类型的语言,随便写一个数它都是有类型固定的。 词法元素(记…...
2024/5/3 2:28:39 - Spring Boot 跟 @WebMvcTest 测试 MVC Web Controller
在有关使用 Spring Boot 进行测试的系列的第二部分中,我们将了解 Web 控制器。首先,我们将探索 Web 控制器的实际作用,这样我们就可以构建涵盖其所有职责的测试。 然后,我们将找出如何在测试中涵盖这些职责。只有涵盖了这些职责&…...
2024/5/3 2:28:35 - 单链表节点的删除与排序.cg
问题描述】输入一组整数,以单链表的形式存储,删掉里面存放偶数的节点,并按照从小到大的顺序排序,并输出 【输入形式】整数序列,以空格为间隔,最多十个整数 【输出形式】整数序列,以空格为间隔 【…...
2024/5/3 2:28:32
最新文章
- FFmpeg计算图像的SSIM的原理
SSIM算法基于HVS更擅长从图像中提取结构信息的事实,并且利用结构相似度来计算图像的感知质量。 在Z. Wang等人的论文Multi-scale structural similarity for image quality assessment中也提到, S S I M SSIM SSIM算法要好于当时的其它的感知图像质量指标…...
2024/5/4 18:53:28 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - IOS手机耗电量测试
1. 耗电量原始测试方法 1.1 方法原理: 根据iPhone手机右上角的电池百分比变化来计算耗电量。 1.2实际操作: 在iOS通用设置中打开电池百分比数值显示,然后操作30分钟,60分钟,90分钟,看开始时和结束时电池…...
2024/4/30 3:14:28 - 【Java】假如把集合体系看作购物中心
购物中心入口:Java集合框架 “Java集合广场”的购物中心,这是一个集合了各种奇特商店的地方,每个商店都充满了不同的宝藏(数据结构)。 一楼:基础集合区 - Collection接口 一楼是基础集合区,这…...
2024/5/2 2:40:27 - Python语法总结:not(常出现错误)
0、not是什么 在python中not是逻辑判断词,用于布尔型True和False之前 a not Ture # a False b not False # b True1、not的用法 (1)判断语句 if not a:# 如果a是False,执行的语句(2)判断元素是否在…...
2024/5/2 5:16:56 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/5/1 17:30:59 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/5/2 16:16:39 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/4/29 2:29:43 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/5/3 23:10:03 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/4/27 17:58:04 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/4/27 14:22:49 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/4/28 1:28:33 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/4/30 9:43:09 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/4/27 17:59:30 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/5/4 18:20:48 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/4/28 1:34:08 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/4/26 19:03:37 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/4/29 20:46:55 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/4/30 22:21:04 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/5/1 4:32:01 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/5/4 2:59:34 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/4/28 5:48:52 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/4/30 9:42:22 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/5/2 9:07:46 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/4/30 9:42:49 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57