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 half
Way = 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;}}}}
评论