第20章 线程同步
监视器:
java所使用的同步机制是监视器,java中的监视器支持两种线程:互斥和协作。java虚拟机通过对象锁来实现互斥,允许多个线程在同一个共享数据上独立而互不干扰地工作。协作则是通过Object类的wait、notify和notifyAll方法来实现,允许多个线程为了同一个目标而共同工作。
除了与数据关联之外,监视器还会关联到一些或更多的代码,这样的代码被称作监视区域。对于一个监视器来说,监视区域是最小的、不可分割的代码块。换句话说,在同一个监视器中,监视区域只会同时被一个线程执行,即使同时有多个并发的线程,监视器会保证在监视区域上同一时间只会执行一个线程。一个线程想要进入监视器的唯一途径就是到达监视器所关联的一个监视区域的开始处,而线程想要继续执行监视区域的唯一途径就是获得该监视器。
当一个线程到达一个监视区域的开始出,它就会被放置到该监视器的入口区。如果没有其他线程在入口区总等待,也没有线程正持有监视器,则这个线程就可以获得监视,并继续执行监视区域中的代码;当这个线程执行完监视区域后,它就会退出并释放该监视器。如果已经有线程持有该监视器,则这个刚刚到达的线程必须在入口区等待,当监视器的持有者退出监视器后,新到达的线程必须与其他已经在入口区等待的线程进行一次比赛,最终只会有一个线程赢得比赛并获得监视器。
互斥帮助线程在访问共享数据时不被其他线程干扰,而协作帮助线程与其他线程共同工作。java虚拟机所使用的这种监视器被称作“等待并唤醒”监视器(也被称作“发信号并继续”)。在这种监视器中,一个已经持有监视器的线程,可以通过一个等待命令,暂停自身的执行;当线程执行了等待命令后,它会释放监视器,并进入一个等待区,这个线程会在等待区一直持续暂停状态,直到这个线程中的其他线程执行了唤醒命令。当
一个线程执行了唤醒命令后,它会继续持有监视器,直到它主动释放监视器(执行一个等待命令或者执行完监视区域的代码)。当执行唤醒的线程释放了监视器后,等待线程才会苏醒,并重新获得监视器。
唤醒线程在它将监视器保护数据值为等待线程想要的状态后执行唤醒命令,但是因为唤醒线程会继续执行,它可能会在执行唤醒后又修改了数据的状态,让等待线程不能继续工作。另一种情况是,第三个线程可能在唤醒线程释放了监视器,而等待线程还没有获得监视器之前抢先获得监视器,而这个线程可能会修改监视器保护的数据的状态。因为以上事实,一次唤醒往往被等待线程看作是一次提醒,告诉它“数据已经是你想要的状态了”。每次等待线程苏醒并获得监视器的时候,它都需要再次检查数据的状态,以确定是否可以继续完成工作;如果数据不是他所需要的状态,这个线程可能会再次执行等待命令或者放弃等待退出监视器。
活动线程会通过两条途径释放监视器:执行一个等待命令,或者完成它正在执行的监视区域。
如果一个监视器的持有者在它释放监视器前没有执行唤醒命令(同时在此之前也没有任何等待线程被唤醒并等待苏醒),那么位于入口区的线程将竞争获得监视器。如果当前持有者执行了唤醒命令,那么入口区中的线程就不得不与一个或多个等待区中的线程竞争。一个线程只有在它正持有监视器时才能执行等待命令,而且它只能通过再次成为监视器的持有者才能离开等待区。
在java虚拟机中,线程在执行等待命令时可以随意指定一个暂停时间,如果在暂停时间截止之前没有其他线程执行唤醒命令,那么这个等待线程会从虚拟机中得到一个自动唤醒的命令,也就是说,在暂停时间到了之后,即使没有来自其他线程的明确的唤醒命令,它也会自动苏醒。
java虚拟机提供了两种唤醒命令:notify和notifyAll。notify命令随机从等待区中选择一个线程并将其标志为可能苏醒,而notifyAll命令会将等待区中的所有线程都标志为可能苏醒。
java虚拟机如何从等待区以及入口区选择下一个线程来执行,在很大程度上取决于java虚拟机的设计者。程序员必须不依赖任何特定的有关优先级的算法或安排。只有当绝对确认只会有一个线程在等待区中挂起的时候,才应该使用notify;只要存在同时有多个线程在等待区中被挂起的可能性,就应该使用notifyAll。
对象锁:
java虚拟机的一些运行时数据区会被所有的线程共享,其他的数据是各个线程私有的。因为堆和方法区是被所有线程共享的,java程序需要为两种多线程访问的数据进行协调:保存在堆中的实例变量、保存在方法区中的类变量。程序不需要协调保存在java栈中的局部变量,java栈中的数据是属于线程私有的。
在java虚拟机中,每个对象和类在逻辑上都是和一个监视器相关联的。对于对象,监视器保护对象的实例变量;对于类,监视器保护类的类变量。如果一个对象没有实例变量,或者一个类没有类变量,相关联的监视器就什么都不监视。
为了实现监视器的排他性监视能力,java虚拟机为每一个对象和类都关联一个锁(互斥体mutex)。线程访问实例变量或者类变量不需要获取锁;但是如果线程获取了对象或类的锁,那么在它释放这个锁之前,就没有其他线程可以获取同一个对象或类的锁(锁住一个对象就是获取相关联的监视器)。
类的锁实际上用对象锁实现,当java虚拟机装载一个class文件的时候,它会创建一个java.lang.Class类的实例来代表该类型;当锁住一个类的时候,实际上锁住的是那个类的Class对象。
一个线程可以允许多次对同一个对象上锁。对于每一个对象来说,java虚拟机维护一个计数器,记录对象被加了多少次锁。没有被锁的对象的计数器是0,线程每加锁一次,计数器加1(只有已经拥有这个对象的锁的线程才能对该对象再次加锁,在它释放锁之前,其他线程不能对这个对象加锁);线程每释放一次锁,计数器就减1。当计数器为0的时候,锁就被完全释放了,其他的线程才可以使用它。
java虚拟机中的一个线程在它到达监视区域开始处的时候请求一个锁。java中有两种监视区域:同步块和同步方法。当线程到达监视区域的第一条指令的时候,线程必须对该引用对象加锁,否则线程不允许执行其中的代码。一旦它获得了锁,线程就进入被保护的代码;当线程离开这块代码时,不管它是如何离开的,它都会释放相关对象上的锁。
java程序员不需要自己动手加锁,对象锁是在java虚拟机内部使用的。在java程序中,只需要编写同步块或者同步方法就可以标志一个监视区域。当java虚拟机运行程序的时候,每一次进入一个监视区域的时候,它都会自动锁上对象或者类的Class对象。
同步语句块:方法内的同步语句块会使用monitorenter和monitorexit操作码。当虚拟机遇到monitorenter的时候,它获得栈中objectref所引用对象的锁,如果线程已经拥有那个对象的锁,锁的计数器加1。线程中的每条monitorexit都会引起计数器减1。class文件的字节码中总会使用catch子句来确保被加锁的对象将被释放,即使从同步语句块中抛出异常。不管被同步的语句块是如何退出的,线程进入这个块时获得的锁总是一定会被释放的。
同步方法:java虚拟机调用同步方法或者从同步方法中返回没有使用任何特别的操作码。当虚拟机解析对方法的符号引用时,它判断这个方法是否是同步的;如果是同步的,虚拟机就在调用方法之前获取一个锁。当同步方法执行完毕的时候,不管是正常结束还是抛出异常,虚拟机都会释放这个锁。
同步方法的类在编译为字节码时,没有使用进入和离开监视的指令,也没有为方法创建同步块的异常表,所以更加高效。
Object类中的协调支持:Object类声明了5个方法,用来访问java虚拟机同步的协调支持。这个方法都是被声明成public final的,所以它们被所有类继承。只有在同步语句块或者同步方法中才能调用这些方法。换句话说,在这些方法被调用的时候,相关联的对象必须已经被加锁了。
相关推荐
Java多线程笔记是 Java 编程语言中关于多线程编程的笔记,涵盖了线程基础知识、线程优先级、线程状态、守护线程、构造线程、线程中断等多方面的内容。 获取简单 main 程序中的线程 在 Java 中,可以使用 ...
在深入探讨Java虚拟机(JVM)如何处理线程间的资源同步与交互机制之前,我们先来明确几个关键概念:线程、多线程、同步、并发以及它们在Java中的实现方式。Java作为一种广泛应用于分布式系统开发的编程语言,其内部...
这些示例有助于理解Java中多线程运行的原理以及如何创建线程和控制线程同步。 通过理解上述知识点,我们可以更好地设计和实现多线程程序,确保程序在并发环境下既能够高效地执行多任务,也能够正确地处理共享数据,...
今天,我们将要学习Java多线程编程的基础知识,包括多线程原理、线程状态、线程同步等内容。 一、多线程原理 多线程编程的原理是指在一个进程中可以同时执行多个线程,每个线程都可以独立地执行不同的任务。Java...
3. **锁记录**:用于实现线程间的同步。 4. **程序计数器**:当前线程所执行的字节码的行号指示器。 5. **方法区**:存储已被虚拟机加载的类信息、常量、静态变量等数据。 #### 七、Java指令集解析 Java字节码指令...
线程同步和通信机制如synchronized关键字、wait()、notify()方法以及Lock接口,是实现并发的关键。 8. **类加载机制**:双亲委派模型是JVM加载类的默认机制,它保证了类加载的唯一性,避免了类的重复加载和冲突。 ...
《JVM:深入理解Java虚拟机》是一本深入解析Java虚拟机工作原理和技术细节的经典书籍。这份学习笔记将涵盖JVM的关键概念、架构以及它如何影响Java程序的性能。我们将探讨以下几个方面: 1. **JVM概述** Java虚拟机...
#### 四、线程同步与死锁 - **资源竞争问题**: - 当多个线程访问共享资源时,可能会出现资源竞争的情况。 - 解决方案包括使用同步关键字如`synchronized`,或者显式锁`Lock`等。 - **死锁**: - 发生在两个或...
描述中提到,“从JVM&JMM角度讲多线程”,意味着资源会讲解Java虚拟机(JVM)和Java内存模型(JMM)如何支持多线程。JVM是Java程序的运行环境,它管理内存和线程的执行。JMM则规定了线程如何共享和访问内存,确保多...
#### 五、线程同步与死锁 由于多个线程共享同一份堆内存,因此在多线程环境中,数据一致性成为一个重要问题。Java提供了多种同步机制来防止数据竞争: 1. **同步代码块**:使用`synchronized`关键字修饰代码块,...
Java线程还有守护线程(daemon threads)的概念,守护线程不会阻止Java虚拟机的退出,只有当所有非守护线程结束时,Java虚拟机才会停止运行。默认情况下,由主线程创建的线程是非守护线程。 Java线程的生命周期包括...
8. **《深入理解JVM虚拟机笔记》**:深入探讨JVM的工作原理,如内存区域、类加载机制、垃圾回收算法、性能调优策略等。 9. **《高级MySQL笔记》**:高级MySQL学习可能涉及分区、视图、触发器、存储过程、复制与集群...
- **run()**:线程执行的主要代码段,由Java虚拟机调用。 - **Thread.yield()**:让当前线程暂停,让其他线程有机会执行,但并不保证一定切换到其他线程。 - **join()**:让调用线程等待目标线程完成。调用线程会...
Java在并发处理方面表现出色,笔记会讲解线程的创建与管理,包括Thread类和Runnable接口的使用,线程同步机制(synchronized、wait()、notify()、Lock接口等),以及线程池的使用和优化。 【IO与NIO】 在输入输出...
《深入理解Java虚拟机》是Java开发者们深入探讨Java运行机制的经典之作,作者周志明以其深入浅出的讲解方式,揭示了Java虚拟机(JVM)的工作原理。本资源包含该书第三版的源码分析及学习笔记,旨在帮助读者更透彻地...
11. **线程并发**:JVM如何支持多线程,包括线程同步机制如synchronized、Lock等,以及线程池的使用和优化。 通过观看"jvm视频",你可以直观地了解这些概念,并通过"jvm笔记"加深理解和记忆。理论学习后,实践操作...
笔记将介绍线程的创建方式、同步机制(如synchronized关键字、wait/notify、Lock接口)、线程池的使用等。 6. **网络编程**:Java提供Socket编程接口,可用于实现客户端/服务器模型的网络通信。笔记会讲解TCP和UDP...
6. **多线程**:Java提供了强大的多线程支持,笔记可能会讲解线程的创建方式(如继承Thread类和实现Runnable接口)、线程同步(如synchronized关键字,wait()、notify()方法,以及Lock接口)。 7. **网络编程**:...
笔记会解释如何创建和管理线程,以及同步机制如synchronized关键字和wait/notify机制。 IO流和NIO(非阻塞I/O)是Java处理输入输出的重要工具。笔记会讲述流的分类、读写操作、文件操作,以及NIO的新特性,如通道、...