Java内存模型与线程(二)线程的实现和线程的调度

先行先发生原则(happen-before原则)

先行先发生是指Java内存模型中定义的两项操作之间的偏序关系。

如果说A先行于B,其实就是说在发生B操作之前,操作A产生的影响能被操作B观察到,至于这个影响可以是修改内存中的共享变量也可以是发送消息、调用某个方法等。

happen-before要求前一个操作的执行结果对后一个操作可见,并且前一个操作按照顺序排在第二个操作之前。

happen-before规则:

  1. **程序次序规则:**在一个线程内,按照程序代码顺序,书写在前面的操作要先行发生于书写在后面的操作。准确的说,应该是控制流顺序而不是程序代码的顺序。
  2. 管程锁定规则:一个unlock操作要先行发生于lock。这里需要强调的是通一把锁。
  3. volatile变量规则:对一个volatile变量的写操作线性发生于后面对这个变量的读操作,后面是指时间的先后顺序。
  4. 线程启动规则:Thread对象的start方法先行发生于此线程的每个动作。
  5. 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测,比如线程A中执行ThreadB.join();那么线程B中的任意操作先行于A从ThreadB.join()操作成功返回。
  6. 线程中断规则:对线程Thread.interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
  7. 对象终结规则:一个对象的初始化完成要先行于他的finalize()方法的开始。
  8. 传递性:如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于C。

时间先后顺序与happen-before原则之间基本没有太大的关系,所以我们衡量并发安全问题的时候不要受到时间顺序的干扰,一切以happen-before原则为准。

实现线程的三种方式

线程是比进程更轻量的调度执行单位,线程的引入是把进程的资源分配和调度分开,进程是资源分配的基本单位,线程以后成为了执行调度的基本单位

实现线程的的方式主要有三种,分别是内核线程实现,用户线程实现,用户线程和轻量级进程组合实现

使用内核线程实现

内核线程是由操作系统内核支持的线程,内核通过调度器来调度内核线程,并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身。支持多线程的内核叫做多线程内核。

程序不会直接使用内核线程而是通过接口–轻量级进程来调用。每个轻量级进程都对应一个内核线程,所以他们之间的比例是1:1的关系。

在这里插入图片描述

使用轻量级进程实现线程的优点:因为有内核线程的支持,每个轻量级进程都会成为一个独立的调度单位,即使其中有一个轻量级进程在系统调用过程中阻塞了,也不会影响整个进程继续工作。

使用轻量级进程实现线程的缺点:轻量级进程需要很多次的系统调用,系统调用的代价是很高的,操作系统需要不停的在用户态和 内核态之间进行切换。每个轻量级进程都有一个内核线程的支持,因此轻量级线程耗费的不少内核资源,所以说内核中的轻量级进程的数量是有限的。

使用用户线程实现

从广义上讲,线程不是内核线程就是用户线程,其实要这么来说,轻量级进程也是用户线程。

从狭义上讲。用户线程是指完全建立在用户空间的线程库上,系统内核不会感知到用户线程的存在,其中用户线程的同步,创建,销毁都在用户态下进行,不需要内核的参与。因此这种实现是非常快速并且是低消耗的。所以部分高性能的数据库中的多线程就是由多用户线程实现的。这种实现方式下的进程和线程之比是 1:N。也称为一对多线程模型。

在这里插入图片描述
这种实现方式的优点:不需要内核的帮助,快速高效且低消耗。
缺点:由于没有内核参与所以考虑的问题是非常多的,导致实现过程过于复杂。

用户线程和轻量级进程混合实现

线程除了以上两种实现方式外,还有用户线程和轻量级进程的混合使用。在这种模式下,既存在用户线程也存在轻量级进程。操作系统提供轻量级进程作为用户线程和内核线程之间的桥梁。这样可以使用内核提供的线程调度功能以及处理器映射,并且用户线程的系统调用通过轻量级进程来完成,大大奖励了整个进程被完全阻塞的风险,在这种模式下,用户线程和轻量级进程的数量都是不确定的,所以是N:M 的关系。Unix系列的操作系统就提供了这种实现模式。
在这里插入图片描述

Java线程的实现

在JDK1.2之前是基于称为“绿色线程”的用户线程来实现的。在JDK1.2中,线程模型改为基于操作系统的线程模型,换句话说,操作系统选择哪种线程模型,Java虚拟机就选择哪种线程映射方式。线程模型只对线程的并发规模和操作成本产生影响,对Java的运行过程来说这些差异是感受不到的。

在Windows和Linux操作系统上,两个操作系统都选择的是1:1线程模型,一条Java线程就映射到一条轻量级进程中,因为Windows和Linux系统提供的线程模型就是一对一的。

Java线程的调度

线程调度就是系统为线程分配处理器使用权的过程,主要分为两种,一种是协同式,一种是抢占式调度

如果使用系协同式调度,那就是线程把自己的任务执行完成后才切换到另一个线程,线程占用处理器的时间由线程自身决定。协同式的好处就是实现简单,切换线程对线程本身来讲是可知的,所以没有什么线程同步问题。
它的缺点也很明显,线程执行时间不受限制,如果一个线程编写的有问题,那他将一直占据处理器的使用,程序一直阻塞在那儿,相当不稳定。

抢占式调度中的每个线程的执行时间交由系统来决定,线程之间的切换不是由线程自身决定。在这种实现线程调度的方式下,线程的执行时间是系统可控的,也不会有一个线程阻塞导致整个进程阻塞的问题,而Java就是采用的抢占式线程调度。

我们可以“建议”系统给某些线程分配多一点执行时间,给一些线程分配少一些执行时间,整个可以通过设置线程优先级来实现。Java一共设置的有10个线程优先级,在两个线程同时处于就绪态的时候,优先级越高的线程越容易被系统选中执行。不过有时候这种采用优先级的方式又不太靠谱,因为Java的线程最后是映射到系统的原声线程来实现的,所以线程调度最终取决于操作系统。如果操作系统中的优先级设置的种类比Java设置的优先级的种类少,那么就会出现几个优先级相同的情况了。例如Windows设置的线程优先级就只有7种,Java设置的有10种,所以Windows至少得重复3种才可以和Java进行对应。

Java线程状态之间的转换

具体看我这篇博文

Java线程的状态以及之间的转换

参考:《深入理解Java虚拟机 第二版》周志明

猜你喜欢

转载自blog.csdn.net/qq_43672652/article/details/106955874
今日推荐