文章目录
并发编程的重要术语
术语名称 | 英文 | 解释 |
---|---|---|
CAS | Compare And Swap(比较并交换) | CAS操作需要输入两个数值,旧值和新值。在操作期间先看旧值有无变化,若没变化,才交换成新值。若旧值变了,则不交换 |
原子性 | 一个或多个操作,要么都成功,要么都失败。中间不能由于任何原因中断。 | |
举例:给人转账有两个操作,一是自己的钱减少,二是对方的钱变多,转账这个过程就要求是原子性的。 | ||
原子性
可见性
有序性(顺序性)
Volatile关键字
(1)Volatile关键字的工作原理
- 将当前处理器缓存行的数据写回到系统内存。
- 个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
即当一个变量被 volatile 修饰时,任何线程对它的写操作都会立即刷新到主内存中(而不是Cache),并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。
注:
(2)Volatile的作用
- 如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。
- volatile 修饰之后并不是让线程直接从主内存中获取数据,依然需要将变量拷贝到工作内存(Cache)中。
- volatile 并不能保证线程安全性。
(3)Volatile的特点
- 可见性
对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。 - 原子性
对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
synchronized的定义与实现原理
1.synchronized关键字的三种用法
(1)修饰普通同步方法
- 锁是当前实例对象
进入同步代码快之前要获得当前对象实例的锁。
(2)修饰静态同步方法
- 锁是当前类的Class对象
会作用于类的所有对象实例。(因为静态成员不属于任何一个实例对象,是类成员(如static表明这个成员是该类的一个静态资源,不管new了多少对象,static是公用的,只有一份))。
注:
因此,若线程A调用一个实例对象的非静态synchronized方法,而线程B需要调用这个实例对象所属类的静态synchronized方法,是不会发生排斥现象。
(3)修饰同步代码块
- 锁是synchronized括号里配置的对象
进入同步代码前要获得给定对象的锁。
2.synchronized关键字的底层实现原理(JVM)
进入同步代码时需要获得锁,在退出或抛出异常时释放锁。
(1)同步代码块的底层实现
- monitorenter
编译后插入到同步代码块的开始位置。 - monitorexit
插入到方法结束处和异常处。 - 实现过程
执行monitorenter指令,线程视图获取锁(即moniter,存在与对象头中。当计数器为0则可以获取(获取后计数器为1))。执行moniterexit指令后,释放锁(计数器置为0)。
(2)同步方法的底层实现
- ACC_SYNCHRONIZED
该标识指明了该方法是一个同步方法。
volatile和Synchronixed的比较
原子性(线程安全问题) | 可见性 | 重排序 | |
---|---|---|---|
volatile | 不保证 | 保证 | 禁止 |
Synchronixed | 保证 | 保证 | 允许 |
锁的升级与对比
-
升级目的
为了减少获得锁和释放锁带来的性能消耗。 -
锁主要存在四中状态
无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。(锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率) -
三种锁的对比
锁 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不需要额外的消耗 | 若线程间存在竞争,会带来额外的锁撤销的消耗 | 只有一个线程访问同步块的场景 |
轻量级锁 | 竞争的线程不会阻塞 | 若始终得不到锁竞争的线程,使用自旋会消耗cpu | 追求响应时间,同步块执行速度非常快 |
重量级锁 | 线程竞争不使用自旋,不会消耗cpu | 线程阻塞,响应时间缓慢 | 追求吞吐量,同步块执行速度较长 |
1.偏向锁
(1)作用
在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。
(2)偏向锁加锁
在一个线程获取锁时,会在对象头的Mark Word部分记录线程ID。(表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁)。以后该线程在加解锁时,只需要简单的测一下MarkWord中的ID即可。
2.轻量级锁
java实现原子操作
通过锁和循环CAS的方式来实现。
1.CAS(比较和交换)
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做(通过调用JNI的代码实现的)
2.CAS实现原子操作的三大问题
-
ABA问题
执行CAS时,若V从A→B→A。CAS会认为V没变化,实际上V变化了。(解决办法,追加版本号,每次V变化的时候版本号+1) -
循环时间长开销大
自旋CAS若长时间不成功,会给CPU带来巨大开销。 -
只能保证一个共享变量的原子操作
对多个共享变量操作,循环CAS不能保证操作的原子性,此时可以使用锁。(锁机制保证只有获得锁的线程才能操作锁定的内存区域)