尚学堂 老师好!

上海:15201841284

广州:020-2989 6995

深圳:0755-23061965

武汉:027-8798 9193

西安:029-8822 8155

JVM 进行线程同步背后的原理

  所有的 Java 程序都会被翻译为包含字节码的 class 文件,字节码是 JVM 的机器语言。这篇文章将阐述 JVM 是如何处理线程同步以及相关的字节码。

  更多精彩内容以及学习资料,尚学堂论坛bbs.bjsxt.com免费下载。

  线程和共享数据

  Java 的一个优点就是在语言层面支持多线程,这种支持集中在协调多线程对数据的访问上。

  JVM 将运行时数据划分为几个区域:一个或多个栈,一个堆,一个方法区。

  在 JVM 中,每个线程拥有一个栈,其他线程无法访问,里面的数据包括:局部变量,函数参数,线程调用的方法的返回值。栈里面的数据只包含原生数据类型和对象引用。在 JVM 中,不可能将实际对象的拷贝放入栈。所有对象都在堆里面。

  JVM 只有一个堆,所有线程都共享它。堆中只包含对象,把单独的原生类型或者对象引用放入堆也是不可能的,除非它们是对象的一部分。数组也在堆中,包括原生类型的数组,因为在 Java 中,数组也是对象。

  除了栈和堆,另一个存放数据的区域就是方法区了,它包含程序中使用到的所有类(静态)变量。方法区类似于栈,也只包含原生类型和对象引用,但是又跟栈不同,方法区中类变量是线程共享的。

  对象锁和类锁

  正如前面所说,JVM 中的两个区域包含线程共享的数据,分别是:

  堆:包含所有对象

  方法区:包含所有类变量

  如果多个线程需要同时使用同一个对象或者类变量,它们对数据的访问必须被恰当地控制。否则,程序会产生不可预测的行为。

  为了协调多个线程对共享数据的访问,JVM 给每个对象和类关联了一个锁。锁就像是任意时间点只有一个线程能够拥有的特权。如果一个线程想要锁住一个特定的对象或者类,它需要向 JVM 请求锁。线程向 JVM 请求锁之后,可能很快就拿到,或者过一会就拿到,也可能永远拿不到。当线程不需要锁之后,它把锁还给 JVM。如果其他线程需要这个锁,JVM 会交给该线程。

  类锁的实现其实跟对象锁是一样的。当 JVM 加载类文件的时候,它会创建一个对应类java.lang.Class对象。当你锁住一个类的时候,你实际上是锁住了这个类的Class对象。

  线程访问对象实例或者类变量的时候不需要获取锁。但是如果一个线程获取了一个锁,其他线程不能访问被锁住的数据,直到拥有锁的线程释放它。

  管程

  JVM 使用锁和管程协作。管程监视一段代码,保证一个时间点内只有一个线程能执行这段代码。

  每个管程与一个对象引用关联。当线程到达管程监视代码段的第一条指令时,线程必须获取关联对象的锁。线程不能执行这段代码直到它得到了锁。一旦它得到了锁,线程可以进入被保护的代码段。

  当线程离开被保护的代码块,不管是如何离开的,它都会释放关联对象的锁。

  多次锁定

  一个线程被允许锁定一个对象多次。对于每个对象,JVM 维护了一个锁的计数器。没有被锁的对象计数为 0。当一个线程第一次获取锁,计数器自增变为 1。每次这个线程(已经得到锁的线程)请求同一个对象的锁,计数器都会自增。每次线程释放锁,计数器都会自减。当计数器变为 0 时,锁才被释放,可以给别的线程使用。

  同步块

  在 Java 语言的术语中,协调多个线程访问共享数据被称为同步(synchronization)。Java 提供了两种内建的方式来同步对数据的访问:

  同步语句

  同步方法

  同步语句

  为了创建同步语句,你需要使用synchronized关键字,括号里面是同步的对象引用,如下所示:

  class KitchenSync {

  private int[] intArray = new int[10];

  void reverseOrder() {

  synchronized (this) {

  int halfWay = intArray.length / 2;

  for (int i = 0; i < halfWay; ++i) {

  int upperIndex = intArray.length - 1 - i;

  int save = intArray[upperIndex];

  intArray[upperIndex] = intArray[i];

  intArray[i] = save;

  }

  }

  }

  }

  在上面的例子中,被同步块包含的语句不会被执行,直到线程得到this引用的对象锁。如果不是锁住this引用,而是锁住其他对象,在线程执行同步块语句之前,它需要获得该对象的锁。

  有两个字节码monitorenter和monitorexit,被用来同步方法中的同步块。

  字节码操作数描述

  monitorenter无取出对象引用,请求与对象引用关联的锁

  monitorexit无取出对象引用,释放与对象引用关联的锁

  当monitorenter被 JVM 执行时,它请求栈顶对象引用关联的锁。如果该线程已经拥有该对象的锁,计数器自增。每次monitorexit被执行,计数器自减。当计数器变为 0 时,该锁被释放。

  注意:当同步块中抛出异常时,catch语句保证对象锁被释放。不管同步块是如何退出的,JVM 保证线程会释放锁。

  同步方法

  为了同步整个方法,你只需要在方法声明前面加上synchronized关键字。

  class HeatSync {

  private int[] intArray = new int[10];

  synchronized void reverseOrder() {

  int halfWay = intArray.length / 2;

  for (int i = 0; i < halfWay; ++i) {

  int upperIndex = intArray.length - 1 - i;

  int save = intArray[upperIndex];

  intArray[upperIndex] = intArray[i];

  intArray[i] = save;

  }

  }

  }

  JVM 不会使用特殊的字节码来调用同步方法。当 JVM 解析方法的符号引用时,它会判断方法是不是同步的。如果是,JVM 要求线程在调用之前请求锁。对于实例方法,JVM 要求得到该实例对象的锁。对于类方法,JVM 要求得到类锁。在同步方法完成之后,不管它是正常返回还是抛出异常,锁都会被释放。

  更多精彩内容以及学习资料,尚学堂论坛bbs.bjsxt.com免费下载。

  • 北京校区
  • 西安校区
  • 山西校区
  • 武汉校区
  • 长沙校区
  • 深圳校区
  • 上海校区
  • 广州校区
  • 保定招生办
  • 黑龙江项目办

北京京南校区:北京亦庄经济开发区科创十四街6号院1号楼 赛蒂国际工业园
咨询电话:400-009-1906 / 010-56233821
面授课程: JavaEE+微服务+大数据     大数据+机器学习+平台架构     Python+数据分析+机器学习  人工智能+模式识别+强化学习   WEB前端+移动端+服务端渲染

地址:陕西省西安市高新区西安软件园西区创新信息大厦A座三层尚学堂

电话:029-88228155 / 18291433445

山西学区地址:山西省晋中市榆次区大学城大学生活广场万科商业A1座702

武汉学区地址:武汉市东湖高新区光谷金融港B22栋11楼
咨询电话:027-87989193

网址:http://www.cssxt.com/
咨询电话:0731-83072091

深圳校区地址:深圳市宝安区航城大道U8智造产业园U6栋3楼
咨询电话:0755-23061965 / 18898413781

上海尚学堂校区地址:上海市浦东新区城丰路650号
咨询电话:021-67690939

广州校区地址:广州市天河区车陂街道大岗路5号中侨广场2栋321室(四号线车陂站D出口,或brt车陂站)
咨询电话:18948349646

保定招生办公室

地址:河北省保定市竞秀区朝阳南大街777号鸿悦国际1101室

电话:15132423123

黑龙江项目办
地点:哈尔滨市松北区博文路青年部落孵化器1层
电话:15321415678
Copyright 2006-2021 北京尚学堂科技有限公司  京ICP备13018289号-19  京公网安备11010802015183  
网站维护:北京尚学堂科技有限公司昌平分公司