先行先发生原则(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虚拟机 第二版》周志明

Java内存模型与线程(二)线程的实现和线程的调度的更多相关文章

  1. 深入理解JMM(Java内存模型) --(二)重排序

    [转载自并发编程网 – ifeve.com 原文链接:http://ifeve.com/tag/jmm/] 数据依赖性 如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存 ...

  2. java内存模型(线程,volatile关键字和sychronized关键字)

    volatile关键字 用在多线程,同步变量. 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B.只在某些动作时才进行A和B的同步.因此存在A和B不一致的情况. ...

  3. 深入理解Java虚拟机(第三版)-13.Java内存模型与线程

    13.Java内存模型与线程 1.Java内存模型 Java 内存模型的主要目的是定义程序中各种变量的访问规则,即关注在虚拟机中把变量值存储到主内存和从内存中取出变量值的底层细节 该变量指的是 实例字 ...

  4. 多线程并发之java内存模型JMM

    多线程概念的引入是人类又一次有效压寨计算机的体现,而且这也是非常有必要的,因为一般运算过程中涉及到数据的读取,例如从磁盘.其他系统.数据库等,CPU的运算速度与数据读取速度有一个严重的不平衡,期间如果 ...

  5. 对多线程java内存模型JMM

    多线程概念的引入体现了人类重新有效压力寨计算机.这是非常有必要的,由于所涉及的读数据的过程中的一般操作,如从磁盘.其他系统.数据库等,CPU计算速度和数据读取速度已经严重失衡.假设印刷过程中一个线程将 ...

  6. Java内存模型分析

    在学习Java内存模型之前,先了解一下线程通信机制. 1.线程通信机制 在并发编程中,线程之间相互交换信息就是线程通信.目前有两种机制:内存共享与消息传递. 1.1.共享内存 Java采用的就是共享内 ...

  7. 理解JAVA内存模型

    实际上java内存模型是如上图所示一样 每个线程有自己的栈内存,存放共享对象的副本,本地变量 每个线程自己的本地变量是不可见的,但是共享对象对每个线程都是可见的. 如果想实现线程通信的话, 线程对共享 ...

  8. Java 并发系列之三:java 内存模型(JMM)

    1. 并发编程的挑战 2. 并发编程需要解决的两大问题 3. 线程通信机制 4. 内存模型 5. volatile 6. synchronized 7. CAS 8. 锁的内存语义 9. DCL 双重 ...

  9. 第十二章 Java内存模型与线程

    Java内存模型(Java Memory Model,JMM): 主内存与工作内存:Java内存模型主要是定义程序中各个变量的访问规则.Java内存模型规定了所有的变量都存储在主内存(Main Mem ...

  10. 《深入理解Java虚拟机》笔记--第十二章、Java内存模型与线程

    主要内容:虚拟机如何实现多线程.多线程之间由于共享和竞争数据而导致的一系列问题及解决方案. Java内存模型:     Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储 ...

随机推荐

  1. AcWing 1194. 岛和桥

    \(f[s][i][j]\) 表示一条有向路径(不经过重复点),当前路径点集合为 \(s\),最后两个点是 \(j\) → \(i\) 的最大价值 \(g[s][i][j]\) 类似,不过是方案数. ...

  2. BJOI2016 IP地址

    题目链接 Description 给定 \(n\) 个 \(01\) 模式串.\(q\) 次询问: 每次询问给定一个 \(01\) 串: 设给这个串匹配的串是在模式串中存在的他的最长前缀 问 \([a ...

  3. Fabric v2.0中的隐私数据

    文章来源于https://hyperledger-fabric.readthedocs.io/en/release-2.0/ 私有数据集在v1.4中提出,一直使用的是隐私数据集方式,即建立一个隐私数据 ...

  4. STL——容器(Set & multiset) insert 的返回值 和 pair 的用法

    1. 使用 insert 插入时的返回值: 将一个元素插入 (insert) 到 set 或 multiset 中时,如果插入失败返回的类型是一个 pair 的自定类型,insert 源码如下: in ...

  5. 【Django 局域网配置】

    默认方法启动django python manage.py runserver 这时启动的服务只能在本机访问,这是因为服务只向本机(127.0.0.1:8000)提供,所以局域网的其他机器不能访问. ...

  6. 纯HTML + CSS制作个人资料卡

    总体预览: 材料:背景图与头像.jpg IDE:VS Code 外部链接:CDN加速的font-awesome图标 <link rel="stylesheet" href=& ...

  7. K8S安装Kubesphere

    准备工作 安装Helm curl -L https://git.io/get_helm.sh | bash 创建账户 cat > heml-rbac.yaml << EOF apiV ...

  8. 超简单的 Docker部署 SpringBoot项目 步骤

    很久之前就用过,一直没有好好写篇博客,今天就总结一下 创建一个 SpringBoot项目 创建一个SpringBoot项目并打成jar包,结构如图 编写 Dockerfile文件 FROM java: ...

  9. Python炫技操作:五种Python 转义表示法

    1. 为什么要有转义? ASCII 表中一共有 128 个字符.这里面有我们非常熟悉的字母.数字.标点符号,这些都可以从我们的键盘中输出.除此之外,还有一些非常特殊的字符,这些字符,我通常很难用键盘上 ...

  10. Java基础进阶:多态与接口重点摘要,类和接口,接口特点,接口详解,多态详解,多态中的成员访问特点,多态的好处和弊端,多态的转型,多态存在的问题,附重难点,代码实现源码,课堂笔记,课后扩展及答案

    多态与接口重点摘要 接口特点: 接口用interface修饰 interface 接口名{} 类实现接口用implements表示 class 类名 implements接口名{} 接口不能实例化,可 ...