前言

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

线程和共享数据

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

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

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

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

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

对象锁和类锁

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

  1. :包含所有对象
  2. 方法区:包含所有类变量

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

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

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

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

管程

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

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

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

多次锁定

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

同步块

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

  1. 同步语句
  2. 同步方法

同步语句

为了创建同步语句,你需要使用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引用,而是锁住其他对象,在线程执行同步块语句之前,它需要获得该对象的锁。

有两个字节码monitorentermonitorexit,被用来同步方法中的同步块

字节码 操作数 描述
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 要求得到类锁。在同步方法完成之后,不管它是正常返回还是抛出异常,锁都会被释放。

JVM 进行线程同步背后的原理的更多相关文章

  1. java 线程同步 原理 sleep和wait区别

    java线程同步的原理java会为每个Object对象分配一个monitor, 当某个对象(实例)的同步方法(synchronized methods)被多个线程调用时,该对象的monitor将负责处 ...

  2. Java 线程同步组件 CountDownLatch 与 CyclicBarrier 原理分析

    1.简介 在分析完AbstractQueuedSynchronizer(以下简称 AQS)和ReentrantLock的原理后,本文将分析 java.util.concurrent 包下的两个线程同步 ...

  3. 你知道购买车票的原理吗?Java 线程同步

    先看再点赞,给自己一点思考的时间,如果对自己有帮助,微信搜索[程序职场]关注这个执着的职场程序员.我有什么:职场规划指导,技能提升方法,讲不完的职场故事,个人成长经验. 大周末的还是6点起床,起床的第 ...

  4. Java线程:概念与原理

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  5. JAVA基础知识之多线程——线程同步

    线程安全问题 多个线程同时访问同一资源的时候有可能会出现信息不一致的情况,这是线程安全问题,下面是一个例子, Account.class , 定义一个Account模型 package threads ...

  6. Java线程(二):线程同步synchronized和volatile

    上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程 ...

  7. 【总结】Java线程同步机制深刻阐述

    原文:http://hxraid.iteye.com/blog/667437 我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Thread ...

  8. Java并发——线程安全、线程同步、线程通信

    线程安全 进程间"共享"对象 多个“写”线程同时访问对象. 例:Timer实例的num成员,即add()方法是用的次数.即Timer实例是资源对象. class TestSync ...

  9. Java多线程(二) —— 线程安全、线程同步、线程间通信(含面试题集)

    一.线程安全 多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性,就可以称作是线程安全的. 讲到线程安全问题,其实是指多线程环境下对共享资源的访问可能会 ...

随机推荐

  1. UIApplication sharedApplication 的常用使用方法-b

    下面是这个类的一些功能:1.设置icon上的数字图标 //设置主界面icon上的数字图标,在2.0中引进, 缺省为0 [UIApplicationsharedApplication].applicat ...

  2. user is not mapped

    用Hibernate实现一个用户的登陆过程,当我在JSP的登陆页面输入姓名和密码,点登陆后,显示登陆失败页,在服务器里显示如下的错误信息:  org.hibernate.hql.ast.QuerySy ...

  3. Hadoop集群(第5期)_Hadoop安装配置

    1.1 Hadoop简介 Hadoop是Apache软件基金会旗下的一个开源分布式计算平台.以Hadoop分布式文件系统(HDFS,Hadoop Distributed Filesystem)和Map ...

  4. Javascript事件绑定的几种方式

    Javascript的事件绑定主要有四种方法(一下在IE中运行正常,但不保证其他浏览器): [注:onXXX为某一事件,fun为某一function,domId为某一DOM对象id,event类型见后 ...

  5. UVA 11737 Extreme Primitive Society

    非常容易的一个题: 只要判断两种基因相差的最小值就行: #include<cstdio> #include<cstring> #include<algorithm> ...

  6. Android Service 生命周期和使用注意项

    一.基础知识 服务一般分为两种: 1:本地服务, Local Service 用于应用程序内部.在Service可以调用Context.startService()启动,调用Context.stopS ...

  7. 【原创】MIPS·Verilog·FPGA

    时至今日,终于将全部的计划55条MIPS指令在FPGA上全部验证完毕,通过这近一个月的不断的修改调试.修改调试,我对整个流程对MIPS有了深刻的体会和认识.借着刚刚现阶段任务的兴奋和短暂的空闲时间,将 ...

  8. Etcd学习(二)集群搭建Clustering

    1.单个etcd节点(测试开发用) 之前我一直开发测试一直是用的一个Etcd节点,然后启动命令一直都是直接打一个etcd(我已经将etcd安装目录的bin目录加入到PATH环 境变量中),然后启动信息 ...

  9. ununtu卸载软件

    sudo apt-get remove vim

  10. LFS 中文版手册发布:如何打造自己的 Linux 发行版

    您是否想过打造您自己的 Linux 发行版?每个 Linux 用户在他们使用 Linux 的过程中都想过做一个他们自己的发行版,至少一次.我也不例外,作为一个 Linux 菜鸟,我也考虑过开发一个自己 ...