Java 高效并发

Posted jiahu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 高效并发相关的知识,希望对你有一定的参考价值。

Java 高效并发

为了便于移植,Java 多线程内存模型不与硬件关联,不同硬件平台可以使用不同的实现手段

和 CPU 内存与高速缓存做对比 Java 内存模型被分为两大部分:主内存(对应 PC 内存)和工作内存(对应 CPU 高速缓存)

主内存与工作内存之间数据的交互 Java 定义了以下 8 种原子操作(最新的 Java 标准已经采用了新的内存访问协议,但下面 8 中操作也应该了解一下)

  1. lock,标识主内存变量为线程独占
    1. 同一个变量可以被一条线程多次 lock,但也需要同样次数的 unlock 才能解锁
    2. lock 一个变量时会清空工作内存中此变量的值,在使用这个变量前需要执行 load 和 assign 初始化变量
  2. unlock,释放主内存被锁变量
    1. 一个变量实现没有被 lock,那也不允许执行 unlock
    2. 对一个变量执行 unlock 之前,必须先把变量同步到主内存中
  3. read,从主内存中读取变量到工作内存中,以便后续的 load 操作
  4. load,作用于工作内存,把 read 操作从主内存中得到的变量值放入工作内存的变量副本中
  5. use,把工作内存中的变量传递给执行引擎
    1. 只有对一个变量前一个操作是 load 时,当前线程才可以使用 use;只有对一个变量后一个操作是 use 时才可以对变量执行 load 操作
  6. assign,把一个从执行引擎接收到的值赋予工作内存中的变量
    1. 变量在工作内存中改变之后必须把变化同步回主内存
  7. store,把工作内存中的变量传递到主存中
    1. 只有对变量执行 assign 操作后才能执行 store;之后后一个操作是 store 才可以执行 assign 操作
  8. write,作用于主内存,把 store 操作从工资内存中得到的变量值放入主内存中

结合上面 8 中操作如果把一个变量从主内存复制到工作内存,那就要顺序执行 read 和 load 操作,Java 内存模型只要求上述两个操作必须按顺序执行(且不能单独出现 read 或 load,同理 write 和 store),没有要求连续执行,即这两个操作之间可以插入其他指令

volatile

volatile 变量和 C/C++ 中的概念是一致的,有以下两个特点

  1. 对所有线程可见,使用 volatile 变量时所有内存会从主存中刷新这个变量,因为线程对 volatile 变量的使用不是互斥的所以 volatile 变量无法保证线程安全,若要保持 volatile 变量的原子性,需要使用同步手段
  2. 禁止指令重排优化

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 给对象打标记,如果成功就不用调用重量级锁并标记对象已被其他线程占用;而其他线程在使用对象时也会确认对象是否已被占用

在存在竞争的情况下,轻量级锁会比重量级锁耗时

以上是关于Java 高效并发的主要内容,如果未能解决你的问题,请参考以下文章

synchronized学习

[Java并发编程实战]构建一个高效可复用缓存程序(含代码)

Java 高效并发

Java并发学习笔记9-并发基础Demo

Java并发编程学习9-并发基础Demo

编写高效的Java代码:常用的优化技巧