Java 高效并发
为了便于移植,Java 多线程内存模型不与硬件关联,不同硬件平台可以使用不同的实现手段
和 CPU 内存与高速缓存做对比 Java 内存模型被分为两大部分:主内存(对应 PC 内存)和工作内存(对应 CPU 高速缓存)
主内存与工作内存之间数据的交互 Java 定义了以下 8 种原子操作(最新的 Java 标准已经采用了新的内存访问协议,但下面 8 中操作也应该了解一下)
- lock,标识主内存变量为线程独占
- 同一个变量可以被一条线程多次 lock,但也需要同样次数的 unlock 才能解锁
- lock 一个变量时会清空工作内存中此变量的值,在使用这个变量前需要执行 load 和 assign 初始化变量
- unlock,释放主内存被锁变量
- 一个变量实现没有被 lock,那也不允许执行 unlock
- 对一个变量执行 unlock 之前,必须先把变量同步到主内存中
- read,从主内存中读取变量到工作内存中,以便后续的 load 操作
- load,作用于工作内存,把 read 操作从主内存中得到的变量值放入工作内存的变量副本中
- use,把工作内存中的变量传递给执行引擎
- 只有对一个变量前一个操作是 load 时,当前线程才可以使用 use;只有对一个变量后一个操作是 use 时才可以对变量执行 load 操作
- assign,把一个从执行引擎接收到的值赋予工作内存中的变量
- 变量在工作内存中改变之后必须把变化同步回主内存
- store,把工作内存中的变量传递到主存中
- 只有对变量执行 assign 操作后才能执行 store;之后后一个操作是 store 才可以执行 assign 操作
- write,作用于主内存,把 store 操作从工资内存中得到的变量值放入主内存中
结合上面 8 中操作如果把一个变量从主内存复制到工作内存,那就要顺序执行 read 和 load 操作,Java 内存模型只要求上述两个操作必须按顺序执行(且不能单独出现 read 或 load,同理 write 和 store),没有要求连续执行,即这两个操作之间可以插入其他指令
volatile
volatile 变量和 C/C++ 中的概念是一致的,有以下两个特点
- 对所有线程可见,使用 volatile 变量时所有内存会从主存中刷新这个变量,因为线程对 volatile 变量的使用不是互斥的所以 volatile 变量无法保证线程安全,若要保持 volatile 变量的原子性,需要使用同步手段
- 禁止指令重排优化
Java 内存模型要求 lock 等 8 个变量操作都具有原子性,但对 64 位数据类型却定义了比较宽松的规定:允许虚拟机将没有被 volatile 修饰的数据读写操作分为两次 32 位的操作来运行。这就导致了多线程非同步情况下读到半个变量的可能性 ,不过大部分商用虚拟机实现都将 64 位数据的操作也实现为原子操作
Java 与线程
JDK 1.2 前 Java 的线程使用协程实现,之后使用系统原生线程
线程安全与锁优化
互斥是方法,同步是目的。Java 中最基本的互斥同步手段是关键字 synchronized
锁
悲观锁
常见的互斥锁是悲观锁,认为只要使用变量就要上锁,无论变量是否出现了竞争条件。随着 CPU 指令的发展我们可以使用基于冲突检测的并发策略,也就是乐观锁。通俗的讲,先进行操作,如果没有发现竞争就认为操作成功,否则就采取其他补偿措施,比如不断的重试,直到成功为止
自旋锁
线程的挂起与恢复是非常耗时的,如果上锁时间很短,使用自旋锁是非常好的优化手段
自适应自旋锁,自旋的次数按一定的策略动态变化
乐观锁
乐观锁与悲观锁最大的区别是前者会先尝试去修改变量,失败后进行补偿,乐观锁避免了线程的阻塞
CAS
乐观锁示例:CAS(Compare And Swap),下面这段代码是 Java 使用 CAS 实现的变量自增,可以用来说明 CAS 的使用方法
public final int incrementAndGet()
{
for(;;)
{
int current = get();
int next = current+1;
if(compareAndSet(current, next)) // 非互斥,设置失败则不断尝试
{
return next;
}
}
}
CAS 的 ABA 漏洞
两个线程分别使用 CAS 实现变量 val 的修改,假设 val 初始值为 val0,线程 A 将 val 修改为 val1 后又修改为 val0,如果在这个过程中线程 B 先读到的是 val0,在修改时 A 已经完成了 val 从 val1 到 val0 的修改过程,val 的状态其实已经发生了变化,但 B 却没有感知到,这个漏洞被称为 ABA。大部分情况下 ABA 对程序的正常运行没有影响
Java 轻量级锁
Java 中的轻量级锁是和系统提供的锁相对应的,本意是在没有多线程竞争的前提下减少传统重量级锁的使用,以减少互斥带来的损耗
轻量级锁所依据的前提是:对于绝大部分的锁,在整个同步周期内都是不存在竞争的,所以没必要一定要使用重量级锁
轻量级锁会先尝试使用 CAS 给对象打标记,如果成功就不用调用重量级锁并标记对象已被其他线程占用;而其他线程在使用对象时也会确认对象是否已被占用
在存在竞争的情况下,轻量级锁会比重量级锁耗时
原文地址:https://www.cnblogs.com/jiahu-Blog/p/11824847.html