Java并发基础(一) | 8月更文挑战

257 阅读9分钟

这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战

前言

谈谈Java并发是每个程序员必备的基础知识,和数据结构和设计模式一样都是一个程序员基本功的考验。本次也是借掘金本次活动总结一下Java并发的知识框架和Java在我们平时项目中如何使用。
本文主要介绍一下计算机基础理论和Java并发编程一些常见操作系统一些容易混淆的概念

Java并发基础

1. 现代计算机理论模型

现代计算机模型是基于-冯诺依曼计算机模型。

冯诺依曼计算机模型 (1).png

计算机五大核心组成部分

  1. 控制器(Control),是整个计算机的中枢神经,其功能是对程序规定的控制信息进行解释,根据其要求进行控制,调度程序、数据、地址,协调计算机各部分工作及内存与外设的访问等。
  2. 运算器(Datapath),运算器的功能是对数据进行各种算术运算和逻辑运算,即对数据进行加工处理。
  3. 存储器(Memory),存储器的功能是存储程序、数据和各种信号、命令等信息,并在需要时提供这些信息。
  4. 输入(Input system),输入设备是计算机的重要组成部分,输入设备与输出设备合你为外部设备,简称外设,输入设备的作用是将程序、原始数据、文字、字符、控制命令或现场采集的数据等信息输入到计算机。常见的输入设备有键盘、鼠标器等。
  5. 输出(Output system):输出设备与输入设备同样是计算机的重要组成部分,它把外算机的中间结果或最后结果、机内的各种数据符号及文字或各种控制信号等信息输出出来。

2. 进程与线程

2.1 进程

资源分配单位。创建慢,上下文切换开销大

2.2 线程

CPU 调度的基本单位。线程间共享进程资源。

注:在Linux操作系统下面其实是没有线程的概念,线程只是为了迎合开发者,搞出来的一个一个比较容易理解的概念。

3 线程的分类

线程主要可以分为两类

  • 用户线程(User-Level Thread)
  • 内核线程(Kernel-Level Thread)
  • 混合线程模型

这里介绍的这些基础知识后面理解并发原理和开源框架对这些基础架构上面做的一些优化我们就更容易去理解。

3.1 内核级线程

内核级线程:
内核线程模型即完全依赖操作系统内核提供的内核线程(Kernel-Level Thread ,KLT)来实现多线程。在此模型下,线程的切换调度由系统内核完成,系统内核负责将多个线程执行的任务映射到各个CPU中去执行。

程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内线程,才能有轻量级进程。这种轻量级进程与内核线程之间1:1的关系称为一对一的线程模型。

优点:

(1)当有多个处理机时,一个进程的多个线程可以同时执行。 (2) 由于内核级线程只有很小的数据结构和堆栈,切换速度快,当然它本身也可以用多线程技术实现,提高系统的运行速率。

缺点:

(1) 线程在用户态的运行,而线程的调度和管理在内核实现,在控制权从一个线程传送到另一个线程需要用户态到内核态再到用户态的模式切换,比较占用系统资源。(就是必须要受到内核的监控)

3.2 用户级线程

用户级线程
是指不需要内核支持而在用户程序中实现的线程,它的内核的切换是由用户态程序自己控制内核的切换,不需要内核的干涉。

从广义上来讲,一个线程只要不是内核线程,就可以认为是用户线程(User Thread,UT),因此,从这个定义上来讲,轻量级进程也属于用户线程,但轻量级进程的实现始终是建立在内核之上的,许多操作都要进行系统调用,效率会受到限制。

而狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也可以支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。这种进程与用户线程之间1:N的关系称为一对多的线程模型。

优点:

(1) 线程的调度不需要内核直接参与,控制简单。
(2) 可以在不支持线程的操作系统中实现。
(3) 同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起,可以节约更多的系统资源。

缺点:

(1) 一个用户级线程的阻塞将会引起整个进程的阻塞。
(2) 用户级线程不能利用系统的多重处理,仅有一个用户级线程可以被执行。

3.3 混合线程模型

线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式。

在这种混合模式中,用户线程与轻量级进程的数量比是不定的,即为N:M的关系。许多UNIX系列的操作系统,如Solaris、HP-UX等都提供了N:M的线程模型实现。

4. 线程模型

4.1. 1:1的关系称为一对一的线程模型

一般一直使用API或者是系统调用创建的线程均为一对一线程。例如,linux使用clone创建的线程,以及win下使用CreateThread创建的线程。

内核线程模型 (2).png

4.2. 1:N一对多的线程模型

部分高性能数据库中的多线程就是由用户线程实现的。

image.png

4.3. N:M 混合线程模型

在这种混合模式中,用户线程与轻量级进程的数量比是不定的,即为N:M的关系。许多UNIX系列的操作系统,如Solaris、HP-UX等都提供了N:M的线程模型实现。

混合线程模型 (3).png

5. 内核态和用户态

什么是内核态和用户态呢?
简单的说当线程运行在用户空间的时候就是用户态,运行在内核空间的时候就是用户态。

5.1.内核空间、用户空间是什么?

通常32位Linux内核虚拟地址空间划分0-3G为用户空间,3-4G为内核空间(注意,内核可以使用的线性地址只有1G)。注意这里是32位内核地址空间划分,64位内核地址空间划分是不同的。(详细可以参考:高端内存详解)

以4G大小的内存空间为例

image.png

5.2.为什么划分内核空间、用户空间?

在 CPU 的所有指令中,有些指令是非常危险的,如果错用,将导致系统崩溃,比如清内存、设置时钟等。如果允许所有的程序都可以使用这些指令,那么系统崩溃的概率将大大增加。

所以,CPU 将指令分为特权指令和非特权指令,对于那些危险的指令,只允许操作系统及其相关模块使用,普通应用程序只能使用那些不会造成灾难的指令。

比如 Intel 的 CPU 将特权等级分为 4 个级别:Ring0~Ring3。其实 Linux 系统只使用了 Ring0 和 Ring3 两个运行级别(Windows 系统也是一样的)。

当进程运行在 Ring3 级别时被称为运行在用户态,而运行在 Ring0 级别时被称为运行在内核态。

UnixLinux架构图.png

5.3.如何进行状态转换?

比如说在用户态程序下面,需要执行一些只有内核权限的一些操作,比如申请内存,网络读写等等,就需要转换到内核状态下去做

    1. 系统调用,为了使应用程序访问到内核管理的资源例如 CPU,内存,I/O。内核必须提供一组通用的访问接口,这些接口就叫系统调用。
    1. 异常,当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
    1. 外围设备中断,当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会 暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到 内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

参考文档

Linux系统中,为什么需要区分内核空间与用户空间?
linux 用户空间与内核空间——高端内存详解
Linux用户态和内核态怎么理解?
Java线程模型
现代操作系统
PS:了解操作系统底层的实现机制,会让我们比较容易理解一些高性能框架的精髓和设计巧妙所在!