帆的博客

扬帆起航

对象的组合

本章将介绍一些组合模式,这些模式能够使一个类更容易成为线程安全的,并且在维护这些类时不会无意中破坏类的安全性保证。

设计线程安全的类

在设计线程安全类的过程中,需要包含以下三个基本要素:

  • 找出构成对象状态的所有变量
  • 找出约束状态变量的不变性条件
  • 建立对象状态的并发访问管理策略

收集同步需求

要确保的类的线程安全性,就需要确保它的不变性条件不会在并发访问的情况下被破坏,这就需要对其状态进行推断。

阅读全文 »

对象的共享

要编写正确的并发程序,管关键问题在于:在访问共享的可变状态时需要进行正确的管理。本章介绍如何共享和发布对象,从而使它们能够安全地由多个线程同时访问。

可见性

「可见性」是指当一个线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。而普通变量做不到这一点,普通变量的值在线程间传递均需要通过主内存来完成,例如线程A修改一个普通变量的值,然后向主内存进行回写,另外一条线程B在线程A回写完成了之后再从主内存进行读取操作,新变量值才会对线程B可见。

阅读全文 »

线程安全性

在构建文件的并发程序时,必须正确地使用线程和锁,但这些终归只是一些机制。要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问。一个对象是否是线程安全的,取决于它是否被多个线程同时访问。当多个线程访问某个状态变量并且其中有一个线程执行写入操作时,必须采用同步机制来协同这些线程对变量的访问。Java中的主要同步机制是关键字synchronized,它提供了一种独占的加锁方式,但「同步」这个术语还包括volatile类型的变量,显式锁以及原子变量。
如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误。有三种方式可以修复这个问题:

  • 不在线程之间共享该状态变量
  • 将状态变量改为不可变的变量
  • 在访问状态变量时使用同步

什么是线程安全性

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。在线程安全类中封装了必要的同步机制,因此客户端无须进一步采取同步措施。
无状态对象一定是线程安全的。(没有共享数据)

阅读全文 »

在看完了《深入理解Java虚拟机》之后,继续看《Java并发编程实战一书》。
相信在了解虚拟机之后,再来看并发相关知识,能理解得更透彻,书中也讲到,对Java内存模型理解得越深入,就对并发编程掌握得越好。顺道说一下,关于JDK里线程和并发相关类的使用,我主要是通过《Think in Java》学习的,这里就不再介绍基本使用方法了。

简介

线程也被称为轻量级进程(这一部分在《深入理解Java虚拟机》中提到过,点击查看)。在大多数现代操作系统中, 都是以线程为基本的调度单位,而不是进程。

线程的优势

要想充分发挥多处理器系统的强大计算能力,线程可以有效的降低程序的开发和维护成本,同时提升复杂应用程序的性能。

阅读全文 »

线程安全

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。

Java语言中的线程安全

讨论线程安全,就限定于多个线程之间存在共享数据访问这个前提,因为如果一段代码根本不会与其他线程共享数据,那么从线程安全的角度来看,程序是串行执行还是多线程执行对它来说是完全没有区别的。我们将Java语言中各种操作共享的数据分为以下5类:不可变、绝对线程安全、相对线程安全、线程兼容和线程独立。

阅读全文 »

「内存模型」可以理解为在特定的操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。

Java内存模型

Java虚拟机规范中试图定义一种Java内存模型来屏蔽掉各种硬件和操作系统内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。在此之前,主流程序语言(如C/C++等)直接使用物理硬件和操作系统的内存模型,因此,会由于不同平台上内存模型的差异,有可能导致程序在一套平台上并发完全正常,而在另外一台平台上并发访问却经常出错,因此在某些场景就必须针对不同的平台来编写程序。

主内存与工作内存

Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。此处的变量与Java编程中所说的变量有所区别,它包括了实例字段、静态字段和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不会被共享,自然不存在竞争问题。

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的私有工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。(这很好的诠释了volatile关键字的作用和原理)

阅读全文 »

Java程序最初是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为『热点代码』。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器。

HotSpot虚拟机内的即时编译器

解释器与编译器

HotSpot虚拟机是采用解释器与编译器并存的架构。解释器和编译器各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率。

编译对象与触发条件

在运行过程中被即时编译器编译的『热点代码』有两类:

  • 被多次调用的方法
  • 被多次执行的循环体
阅读全文 »

执行引擎是Java虚拟机最核心的组成部分之一,本章将主要从概念模型的角度来讲解虚拟机的方法调用和字节码执行。

运行时栈帧结构

栈帧(Stack Frame)是用于支持虚拟机进行方法代用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法,执行引擎运行的所有字节码指令都只针对当前栈帧进行操作,在概念模型上,典型的栈帧结构图如下:

阅读全文 »

类加载机制

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

类加载的时机

类被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载7个阶段。其中验证、准备、解析3个部分统称为连接,这个阶段的发生顺序如下图所示:

阅读全文 »

Class类文件结构

本章说一下Java编译后的class文件结构。

魔数与Class文件的版本


我这里用sublime打开一个class文件,看到前面4个字节是十六进制0xCAFEBABE,这个是Class文件的魔数.

很多文件存储标准中都使用魔数进行身份识别,因为扩展名可以更改,魔数就是确定这个文件是否为一个能被虚拟机接受的Class文件。

然后看0000 0034,转换成十进制是52,这个表示Java编译的版本号,相信大家在工作中也遇见过Unsupported major.minor version 52.0之类的错误,指的就是这个版本号,52对应的是JDK8。

阅读全文 »
0%