zoukankan      html  css  js  c++  java
  • 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虚拟机 第二版》周志明

  • 相关阅读:
    HDU 3951 (博弈) Coin Game
    HDU 3863 (博弈) No Gambling
    HDU 3544 (不平等博弈) Alice's Game
    POJ 3225 (线段树 区间更新) Help with Intervals
    POJ 2528 (线段树 离散化) Mayor's posters
    POJ 3468 (线段树 区间增减) A Simple Problem with Integers
    HDU 1698 (线段树 区间更新) Just a Hook
    POJ (线段树) Who Gets the Most Candies?
    POJ 2828 (线段树 单点更新) Buy Tickets
    HDU 2795 (线段树 单点更新) Billboard
  • 原文地址:https://www.cnblogs.com/dataoblogs/p/14121904.html
Copyright © 2011-2022 走看看