Java内存模型与线程(二)线程的实现和线程的调度
先行先发生原则(happen-before原则)
先行先发生是指Java内存模型中定义的两项操作之间的偏序关系。
如果说A先行于B,其实就是说在发生B操作之前,操作A产生的影响能被操作B观察到,至于这个影响可以是修改内存中的共享变量也可以是发送消息、调用某个方法等。
happen-before要求前一个操作的执行结果对后一个操作可见,并且前一个操作按照顺序排在第二个操作之前。
happen-before规则:
- **程序次序规则:**在一个线程内,按照程序代码顺序,书写在前面的操作要先行发生于书写在后面的操作。准确的说,应该是控制流顺序而不是程序代码的顺序。
- 管程锁定规则:一个unlock操作要先行发生于lock。这里需要强调的是通一把锁。
- volatile变量规则:对一个volatile变量的写操作线性发生于后面对这个变量的读操作,后面是指时间的先后顺序。
- 线程启动规则:Thread对象的start方法先行发生于此线程的每个动作。
- 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测,比如线程A中执行
ThreadB.join()
;那么线程B中的任意操作先行于A从ThreadB.join()
操作成功返回。 - 线程中断规则:对线程
Thread.interrupt()
方法的调用先行发生于被中断线程的代码检测到中断事件的发生。 - 对象终结规则:一个对象的初始化完成要先行于他的
finalize()
方法的开始。 - 传递性:如果操作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内存模型与线程(二)线程的实现和线程的调度的更多相关文章
- 深入理解JMM(Java内存模型) --(二)重排序
[转载自并发编程网 – ifeve.com 原文链接:http://ifeve.com/tag/jmm/] 数据依赖性 如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存 ...
- java内存模型(线程,volatile关键字和sychronized关键字)
volatile关键字 用在多线程,同步变量. 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B.只在某些动作时才进行A和B的同步.因此存在A和B不一致的情况. ...
- 深入理解Java虚拟机(第三版)-13.Java内存模型与线程
13.Java内存模型与线程 1.Java内存模型 Java 内存模型的主要目的是定义程序中各种变量的访问规则,即关注在虚拟机中把变量值存储到主内存和从内存中取出变量值的底层细节 该变量指的是 实例字 ...
- 多线程并发之java内存模型JMM
多线程概念的引入是人类又一次有效压寨计算机的体现,而且这也是非常有必要的,因为一般运算过程中涉及到数据的读取,例如从磁盘.其他系统.数据库等,CPU的运算速度与数据读取速度有一个严重的不平衡,期间如果 ...
- 对多线程java内存模型JMM
多线程概念的引入体现了人类重新有效压力寨计算机.这是非常有必要的,由于所涉及的读数据的过程中的一般操作,如从磁盘.其他系统.数据库等,CPU计算速度和数据读取速度已经严重失衡.假设印刷过程中一个线程将 ...
- Java内存模型分析
在学习Java内存模型之前,先了解一下线程通信机制. 1.线程通信机制 在并发编程中,线程之间相互交换信息就是线程通信.目前有两种机制:内存共享与消息传递. 1.1.共享内存 Java采用的就是共享内 ...
- 理解JAVA内存模型
实际上java内存模型是如上图所示一样 每个线程有自己的栈内存,存放共享对象的副本,本地变量 每个线程自己的本地变量是不可见的,但是共享对象对每个线程都是可见的. 如果想实现线程通信的话, 线程对共享 ...
- Java 并发系列之三:java 内存模型(JMM)
1. 并发编程的挑战 2. 并发编程需要解决的两大问题 3. 线程通信机制 4. 内存模型 5. volatile 6. synchronized 7. CAS 8. 锁的内存语义 9. DCL 双重 ...
- 第十二章 Java内存模型与线程
Java内存模型(Java Memory Model,JMM): 主内存与工作内存:Java内存模型主要是定义程序中各个变量的访问规则.Java内存模型规定了所有的变量都存储在主内存(Main Mem ...
- 《深入理解Java虚拟机》笔记--第十二章、Java内存模型与线程
主要内容:虚拟机如何实现多线程.多线程之间由于共享和竞争数据而导致的一系列问题及解决方案. Java内存模型: Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储 ...
随机推荐
- 【笔记】「pj复习」深搜——拿部分分
说在最前面 众所周知, NOIP pj 的第三题大部分都是 dp ,但是有可能在考场上想不到动态转移方程,所以我们就可以拿深搜骗分. 方法 深搜拿部分分 剪枝 记忆化 看数据范围 有时候发现,写完深搜 ...
- 算法——移掉K位数字使得数值最小
给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小. leetcode 解题思路:如果这个数的各个位是递增的,那么直接从最后面开始移除一定就是最最小的:如果这个数的 ...
- 图的遍历BFS
图的遍历BFS 广度优先遍历 深度优先遍历 可以进行标记 树的广度优先遍历,我们用了辅助的队列 bool visited[MAX_VERTEX_NUM] //访问标记数组 //广度优先遍历 void ...
- django 验证码
1.django 缓存设置 django的六种缓存(mysql+redis) :https://www.cnblogs.com/xiaonq/p/7978402.html#i6 1.1 安装Djang ...
- PHP 读取XML大文件格式并将其存入数据库中
<?php $xml = new XMLReader(); $xmlfile="./full_database.xml";#文件路径 $xml->open( ...
- 在Linux下下载RPM包
在Linux下下载RPM包,但是不安装 在工作中经常会遇到离线安装RPM包的情况,下面是下载RPM包的方法 # 使用yum下载RPM包 yum -y install --downloadonly &l ...
- Spark内核-内存管理
Spark 集群会启动 Driver 和 Executor 两种 JVM 进程 我们只关注Executor的内存. 分为堆内内存和堆外内存 内存分为 存储内存 : 存储数据用的. 执行内存: 执行sh ...
- linux里用户权限:~$,/$,~#,/#的区别与含义
$表明是非root用户登录,#表示是root用户登录,它们是终端shell的命令提示符几种常用终端的命令提示符 BASH: root账户: # ,非root账户: $KSH: root账户: # ...
- Linux系列之makefile的简单入门
什么是makefile呢? 一个工程中的源文件不计其数,其按类型.功能.模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译, ...
- Python之selenium创建多个标签页
最近在做一个项目,需要用到cookies登录,想法是,在同一个浏览器下,打开两个标签页进行.让其自动获取cookies,先记录,不行的话,到时候再手动加载cookies. 1 ''' 2 #selen ...