京东二面JVM如何实现线程同步问题时该如何回答?

Java编程语言的优势之一是它支持语言级的多线程。大部分支持都集中在协调对多个线程之间共享的数据的访问。

JVM将正在运行的Java应用程序的数据存放到多个运行时数据区域中:一个或多个Java堆栈,堆和方法区。

在JVM内部,每个线程都被分配一个虚拟机栈,其中包含其他线程无法访问的数据,包括线程调用的每个方法的局部变量,参数和返回值。虚拟机栈上的数据仅限于基本类型和对象引用。在JVM中,无法将实际对象的存放在虚拟机栈上,所有对象都分配在堆上。

JVM中只有一个堆,并且所有线程都共享它。堆只包含对象。无法在堆上分配单独的原始类型或对象引用 - 这些东西必须是对象的一部分。数组也分配在堆上,包括基本类型的数组,但在Java中,数组也是对象。

除了虚拟机栈和堆之外,还有方法区,它包含程序使用的所有类(或静态)变量。方法区域类似于堆栈,因为它只包含基本类型和对象引用。但是,与虚拟机栈不同,方法区域中的类变量由所有线程共享。

  • 对象和类锁如上所述,JVM中的两个存储区包含所有线程共享的数据。这些是:堆,包含所有对象
  • 方法区域,包含所有类变量

如果多个线程需要同时使用相同的对象或类变量,则必须很好的控制对数据的访问。否则,程序将出现不可预测的问题。

为了协调多个线程之间的共享数据访问,Java虚拟机将锁与每个对象和类相关联。锁就像一个特权,任何时候只有一个线程可以“拥有”。如果线程想要锁定特定对象或类,它会询问JVM。在线程向JVM发出锁定之后JVM会为线程提供锁定。当线程不再需要锁时,它会将锁归还给JVM。如果另一个线程请求了相同的锁,则JVM将锁传递给另外一个线程。

类锁在实现上为对象锁。当JVM加载类文件时,它会创建一个类实例java.lang.class。锁定类时,实际上是锁定该类的Class对象。

线程无需获取锁来访问实例或类变量。但是,如果一个线程确实获得了一个锁,那么没有其他线程可以访问锁定的数据,直到拥有该锁的线程释放它为止。

监视器

JVM将锁与监视器结合使用。监视器相当于是监护人,它监视一系列代码,确保一次只有一个线程执行代码

每个监视器都与对象引用相关联。当线程到达监视器监视下的代码块中的第一条指令时,线程必须获取对引用对象的锁定。在获得锁之前,不允许线程执行代码。一旦获得锁定,线程就会进入受保护代码块。

当线程离开块时,无论它如何离开块,它都会释放相关对象的锁定。

多个锁

允许单个线程多次锁定同一对象。对于每个对象,JVM维护对象被锁定的次数。解锁对象的计数为零。当线程第一次获取锁定时,计数增加到1。每次线程获取对同一对象的锁定时,计数都会递增。每次线程释放锁定时,计数都会递减。当计数达到零时,锁被释放并可供其他线程使用。

同步块

在Java语言术语中,必须访问共享数据的多个线程的协调称为同步。该语言提供了两种内置方式来同步对数据的访问:使用synchronized语句或同步方法

同步语句

要创建synchronized语句,请使用synchronized带有表达式的关键字,该表达式的计算结果为对象引用,如reverSEOrder()下面的方法所示:

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)上获取锁之前,不会执行synchronized块中包含的语句。如果代替this引用,表达式产生对另一个对象的引用,则在该线程继续之前将获取与该对象相关联的锁。

看一下该类方法生成的字节码信息

两个操作码monitorenter和monitorexit,用于方法中的同步块。

当JVM遇到monitorenterJ时,它获取虚拟机栈上对象引用的对象的锁。如果线程已拥有该对象的锁,则递增计数。

当JVM遇monitorexit时,计数递减。当计数达到零时,监视器被释放。

请注意,即使从同步块中抛出异常,catch子句也可确保锁定的对象将被解锁。无论退出同步块如何,当线程进入块时获取的对象锁定肯定会被释放。

同步方法

要同步整个方法,只需将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获取调用方法的对象关联的锁。对于类方法,它获取方法所属的类关联的锁。在synchronized方法完成后,无论是通过返回还是通过抛出异常来完成,都会释放锁。

希望大家能够支持我的文章,点个关注不迷路,点完关注的小伙伴可以找我私聊领取Java学习资料、大厂面试题库等福利!也欢迎大家在评论区留言交流JVM见解。

相关文章

jinfo 命令可以用来查看 Java 进程运行的 JVM 参数,命令如下...
原文链接:https://www.cnblogs.com/niejunlei/p/5987611.ht...
java 语言, 开发者不能直接控制程序运行内存, 对象的创建都是...
jvm
1.jvm的简单抽象模型:  2.类加载机制     双亲委派模...
堆外内存JVM启动时分配的内存,称为堆内存,与之相对的,在代...
1.springboot和tomcat2.springcloud的请求如何通过网关鉴权?...