Java的并发编程- 缘起之概述

我发现team 的一些成员可以写代码,但是基本功都很不扎实。 有必要从最基础的开始讲起。 从今天开始,根据大家的情况,会开始一些更基础的知识的介绍。 会从我的经验进行梳理和整理并发编程。

概述

几乎任何的操作系统都支持运行多个任务,通常一个任务就是一个程序,而一个程序就是一个进程。当一个进程运行时,内部可能包括多个顺序执行流,每个顺序执行流就是一个线程。

进程

进程是指处于运行过程中的程序,并且具有一定的独立功能。由指令数据和IO操作组成。进程管理如下三个方面:

  1. 指令加载至CPU

  2. 数据加载到内存

  3. 管理操作磁盘、网络等设备。

线程:

线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程。线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不能拥有系统资源。它与父进程的其他线程共享该进程的所有资源。

线程的特点:

  • 一个线程就是一个指令流。讲指令交给CPU逐一完成。线程可以完成一定任务,可以和其它线程共享父进程的共享变量和部分环境,相互协作来完成任务。

  • 线程是独立运行的,其不知道进程中是否还有其他线程存在。

  • 线程的执行是抢占式的,也就是说,当前执行的线程随时可能被挂起,以便运行另一个线程。

  • 一个线程可以创建或撤销另一个线程,一个进程中的多个线程可以并发执行。

  • 在Java中, 线程是作为最小调度单位,进程是最小资源单位。

image.png

并行和并发

单核CPU下,线程实际还是串行执行的,操作系统中有一个组件叫做任务调度器,将CPU的时间片( windows下时间片最小约为15毫秒)分给不同的线程使用,只是由于CPU在线程间(时间片很短)的切换非常快,人类感觉是同时运行的(人类感知的最短时间是0.1秒),总结为一句话就是:微观串行,宏观并行。

一般会将这种线程轮流使用CPU的做法称为并发, Concurrency

CPU 时间片1 时间片2 时间片3 时间片4
Core 线程1 线程2 线程3 线程4
image.png

多核CPU下,每个核(core)都可以调度运行线程,这时候线程可以是并行的,Parallelism

CPU 时间片1 时间片2 时间片3 时间片4
Core1 线程1 线程1 线程3 线程3
Core2 线程2 线程4 线程2 线程4
image.png

Rob Pike (golang 创造者,Unix团队成员,google 工程师)
Concurrency is about dealing with lots of things at once. (同时应对)
Programming as the simultaneous execution of (possibly related) computations. (同时处理)

单线程一定比多线程慢么?

Redis:

  • 使用单线程模型是 Redis 的开发和维护更简单,因为单线程模型方便开发和调试;

  • 即使使用单线程模型也并发的处理多客户端的请求,主要使用的是多路复用和非阻塞 IO;

  • 主要的性能瓶颈是内存或者网络带宽而并非 CPU

  • 但是单线程也有单线程的苦恼,比如需要删除一个很大的数据时,因为是单线程同步操作,这就会导致 Redis 服务卡顿。

Java 线程

  • 创建和运行线程

  • 查看线程

  • 线程API

  • 线程状态

创建和运行线程

  1. New Thread

  2. Runnable (Lambda,Class)

  3. FutureTask 带返回值的Thread

原理之 Thread与 Runnable的关系

分析 Thread的源码,理清它与 runnable的关系

小结

  • 方法1是把线程和任务合并在了一起,方法2是把线程和任务分开了

  • 用 Runnable更容易与线程池等高级API配合

  • 用 Runnable让任务类脱离了 Thread继承体系,更灵活

Java 运行原理

Java Virtual Machine(JVM) 是一种抽象的计算机,基于堆栈架构,它有自己的指令集和内存管理。它加载 class 文件,分析、解释并执行字节码。基本结构如下:

image.png

类加载器子系统

它主要功能是处理类的动态加载,还有链接,并且在第一次引用类时进行初始化。

Loading - 加载

顾名思义,用于加载类,它有三种类加载器,根据双亲委托模型,从不同路径进行加载:

  • Bootstrap ClassLoader - 加载 rt.jar 核心类库,是优先级最高的加载器

  • Extension ClassLoader - 负责加载 jre\lib\ext 文件夹中的类

  • Application ClassLoader -负责加载 CLASSPATH 指定的类库

Linking - 链接

动态链接到运行时所需的资源,分为三步:

  • Verify - 验证:验证生成的字节码是否正确

  • Prepare - 准备:为所有静态变量,分配内存并赋予默认值

  • Resolve - 解析:将 class 文件常量池中所有对内存的符号引用,替换成到方法区的直接引用

Initialization - 类初始

,类加载的最后阶段,这里对静态变量进行赋值,并执行静态块。(注意区分对象初始化)

运行时数据区

它约定了在运行时程序代码的数据比如变量、参数等等的存储位置,主要包含以下几部分:

PC 寄存器(程序计数器)

保存正在执行的字节码指令的地址

在方法调用时,创建一个叫栈帧的数据结构,用于存储局部变量和部分过程的结果,栈帧由以下几部分组成:

  • 局部变量表:存储方法调用时传递的参数,从0开始存储this、方法参数、局部变量

  • 操作数栈:执行中间操作,存储从局部变量表或对象实例字段复制的常量或变量值,以及操作结果,另外,还用来准备被调用方法的参数和接受方法调用的返回结果

  • 动态链接:一个指向运行时常量池的引用,将 class 文件中的符号引用(描述一个方法调用了其他方法或访问成员变量)转为直接引用

  • 方法返回地址:方法正常退出或抛出异常退出,返回方法被调用的位置

  • 堆:存储类实例对象和数组对象,垃圾回收的主要区域

方法区

也被称为元空间,还有个别名 non-heap(非堆),使用本地内存存储 class meta-data 元数据(运行时常量池,字段和方法的数据,构造函数和方法的字节码等),在 JDK 8 中,把 interned String 和类静态变量移动到了 Java 堆。用于存储已经被虚拟机加载的类信息,常量,静态变量等。

运行时常量池

存储类或接口中的数值字面量,字符串字面量以及所有方法或字段的引用,基本上涉及到方法或字段,JVM 就会在运行时常量池中搜索其具体的内存地址

本地方法栈

与 JVM 栈类似,只不过服务于 Native 方法。会抛出StackOverflowError 和OutOfMemoryError。

运行时数据区存储着要执行的字节码,执行引擎将会读取并逐个执行。reference:

https://www.cnblogs.com/yanl55555/p/13334713.html

Java 语言也是混合编译、解释语言, 而不是单纯的预编译语言。

Interpreter

解释器,当Java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“翻译”为对应平台的本地机器指令执行。

  • 解释器真正意义上所承担的角色就是一个运行时“翻译者”,将字节码文件中的内容“翻译”为对应平台的本地机器指令执行。

  • 当一条字节码指令被解释执行完成后,接着再根据PC寄存器中记录的下一条需要被执行的字节码指令执行解释操作。
    在HotSpot VM中,解释器主要由Interpreter模块和Code模块构成。

  • Interpreter模块:实现了解释器的核心功能

  • Code模块:用于管理HotSpot VM在运行时生成的本地机器指令

JIT Compiler

JIT编译器, 解决了解释器的缺点,仍使用解释器来转换字节代码,但发现有代码重复执行时,会使用 JIT 编译器,将整个字节码编译成本地代码,将本地代码用于重复调用,从而提高系统的性能,有以下几部分组成: 中间代码生成器 - 生成中间代码 代码优化器 - 负责优化上面生成的中间代码 目标代码生成器 - 负责生成机器代码或本地代码。 Profiler - 一个特殊组件,负责查找热点,判断该方法是否被多次调用Garbage Collector- 垃圾收集器,收集和删除未引用的对象。

另外,还包括执行引擎所需的本地库(Native Method Libraries)和与其交互的 JNI 接口(Java Native Interface)。**

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容