Java并发编程——底层实现原理

并发编程底层原理

Java程序在执行前会被编译为字节码,字节码文件被加载到JVM中,由JVM来执行字节码文件,最终的执行需要转换为汇编语言在CPU上执行,因此,Java的并发编程底层的实现与JVM的实现和CPU的指令有关。本文将简单讨论下Java并发编程的底层实现原理和Java的内存模型。

volatile和synchronized

在Java的并发编程中,volatile和synchronized有着重要的位置,一般可以认为volatile是synchronized的轻量级实现,volatile在并发编程中可以保证共享变量的可见性。由于volatile不会引起线程的上下文切换和调度,因此相比较synchronized,它的使用和执行成本更加低。

volatile的实现原理和应用

Java语言规范第3版中对volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。
volatile的实现有以下两条原则:

  • 将当前处理器的缓存数据写回到系统的内存中,并且在写回操作执行期间,处理器独占共享内存;
  • 这个写回内存的操作会使其他CPU中缓存了该内存地址的数据变得无效,在下次访问相同内存地址时,强制执行缓存行填充。

    通过保证以上两条原则,就可以实现volatile的轻量级同步以及线程间的可见性。

synchronized的实现原理

synchronized一般被认为是并发编程中的重量级锁。
利用synchronized实现同步,在Java中,任何对象都可以都可以作为锁:

  • 对于普通同步方法,锁是当前执行方法的实例对象;
  • 对于静态方法,锁是当前类的Class对象;
  • 对于同步代码块,锁是synchronized括号里配置的对象。

当一个线程试图访问同步代码块时,必须先获得锁,退出或者抛出异常时,必须释放锁。JVM中,通过进入和退出Monitor对象来实现方法和代码块的同步,任何对象都有一个与之相关的Monitor对象,当一个monitor被持有后,将处于被锁定状态。当线程进入到同步代码块时,将会获得对象所对应的monitor的所有权,从而获得对象的锁。Java对象头中保存着Java对象的锁标记。

Java中原子操作的实现

在Java中可以通过锁和循环CAS实现原子操作。

循环CAS实现原子操作

CAS操作需要输入两个值,一个旧值(期望操作前的值)和一个新值,在操作期间比较旧值有没有发生变换,如果没有发生变换,才变换为新值,如果已经发生了变换,则不进行变换。

  1. 在Java并发包中有一些并发框架使用自旋CAS(循环进行CAS,直到成功为止)的方式来实现原子操作。
  2. 循环CAS实现原子操作存在三大问题:ABA问题(CAS操作检查值是否变化,但是对于中间过程不清楚,因此对于中间有变换的时候会出现ABA问题,解决ABA问题,使用版本号即可),循环时间长开销大,只能保证一个共享变量的原子操作(解决该问题,可以讲多个共享变量合为一个变量来操作,从Java 1.5开始,JDK提供了AtomicReference来保证对象操作的原子性,因此可以使用对象来保存多个共享变量)。
使用锁机制实现原子操作

锁机制保证了只有获得锁的线程才能够操作锁定的内存区域。JVM内部实现了很多种锁机制,有偏向锁、轻量级锁和互斥锁。有意思的是除了偏向锁,JVM实现锁的方式都用了循环CAS,即当一个线程想进入同步块的时候使用循环CAS的方式来获取锁,当它退出同步块的时候使用循环CAS释放锁。

Java内存模型

并发编程需要处理的两个基本问题是线程之间如何进行通信以及线程之间如何同步。通信指的是线程之间以何种机制来交换信息,在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。同步是指程序中用于控制不同线程间操作发生相对顺序的机制。在共享内存并发模型里,同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。
Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。

Java内存模型基础

在Java中,所有实例域、静态域和数组元素都存储在堆内存中,堆内存在线程之间共享,局部变量(Local Variables),方法定义参数(Java语言规范称之为Formal Method Parameters)和异常处理器参数(ExceptionHandler Parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。
线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。
这里写图片描述
Java内存模型属于语言级的内存模型,它确保在不同的编译器和不同的处理器平台之上,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。从JDK 5开始,使用happens-before的概念来阐述操作之间的内存可见性。如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。
下面是happens-before原则规则:
1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
2. 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作;
6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始。

volatile的内存语义

volatile变量自身具有下列特性:
1. 可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
2. 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。

volatile变量的读写具有如下含义:

  1. 当写一个volatile变量时,Java内存模型会把该线程对应的本地内存中的共享变量值刷新到主内存;
  2. 当读一个volatile变量时,Java内存模型会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
  3. 线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过主内存向线程B发送消息。

锁的内存语义

锁是Java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。

  1. 当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。
  2. 当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。
  3. 线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。

Java内存模型的可见性保证

  1. 单线程程序。单线程程序不会出现内存可见性问题。编译器、runtime和处理器会共同确保单线程程序的执行结果与该程序在顺序一致性模型中的执行结果相同。
  2. 正确同步的多线程程序。正确同步的多线程程序的执行将具有顺序一致性(程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同)。这是JMM关注的重点,JMM通过限制编译器和处理器的重排序来为程序员提供内存可见性保证。
  3. 未同步/未正确同步的多线程程序。JMM为它们提供了最小安全性保障:线程执行时读取到的值,要么是之前某个线程写入的值,要么是默认值(0、null、false)。最小安全性保证线程读取到的值不会无中生有的冒出来,但并不保证线程读取到的值一定是正确的。

本文对Java并发编程的底层实现原理和Java的内存模型做了整体的介绍,希望可以读者朋友可以对Java并发编程的底层原理和Java内存模型有一个初步了解,希望本文可以帮助各位在Java并发编程中解决一些原理性的问题。

参考文献

  1. 周志明:《深入理解Java虚拟机》
  2. 方腾飞:《Java并发编程的艺术》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值