主要内容:虚拟机如何实现多线程、多线程之间由于共享和竞争数据而导致的一系列问题及解决方案。
Java内存模型:
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。(这里所说的变量是指可能存在竞争问题的实例字段、静态字段和构成数据对象的元素)
Java内存模型规定了所有的变量都存储在虚拟机内存的主内存(Main Memory)中,每条线程还有自己的工作内存(Working Memory),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在自己的工作内存中进行,而不能直接读写主内存中的变量。
Java内存模型定义了8种操作来完成主内存和工作内存之间的交互:
作用于主内存:lock(锁定)、unlock(解锁)、read(读取)、write(写入)
作用于工作内存: load(载入)、use(使用)、assign(赋值)、store(存储)
规则:需要顺序执行read和load、store和write【不必保证连续执行】。
不允许read和load、store和write操作之一单独出现。
不允许线程丢弃它最近的assign操作,必须同步回主内存。
不允许线程无原因(没有发生过任何assign操作)的把数据同步回主内存。
新变量只能在主内存中诞生,不允许在工作内存中直接使用一个未初始化(load或assign)的变量。
一个变量同一时刻只允许一条线程对其进行lock操作,可多次lock,需要该线程进行等量的unlock操作才会被解锁。
如果对一个变量执行lock操作,将会清除工作内存中此变量的值,在使用此变量前需要重新初始化到工作内存中。
unlock操作必须在lock操作之后,且不可跨线程。
对一个变量unlock操作前,必须先把变量同步回主内存中。
volatile型变量的特殊规则:
关键字volatile是虚拟机提供的最轻量级的同步机制。
volatile变量具备的两种特性:a)变量对所以线程的可见性。b)禁止指令重排序优化。
volatile变量在大多数场景下,总开销仍然要比锁(使用synchronized关键字或java.util.concurrent包里面的锁)来的低。
对于long和double类型的非原子性操作规则:
Java内存模型定义的8种操作都具有原子性,但是对64位的数据(long和double)特别定义了一条规则(非原子性协定):允许未被volatile修饰的64位的数据读写操作划分为两次32位的操作来进行。
原子性、可见性和有序性:
Java内存模型是围绕着在并发过程中如何处理原子性、可见性和有序性这三个特征来建立的。
原子性(Automicity):由Java内存模型来保证的的原子性变量操作包括read(读取)、write(写入)、load(载入)、use(使用)、assign(赋值)、store(存储)这六个。更大范围的原子性保证需要使用到synchronized关键字,即synchronized块之间的操作也具备原子性。
可见性(Visibility):当一个线程修改了共享变量的值,其他线程立即得到这个修改。Java内存模型是通过在变量修改后将变量同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的。可以看做是将read->load->use、assign->store->write组合成两个原子性操作实现可见性。
有序性(Ordering):如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。无序性主要由于存在”指令重排序“和”工作内存与主内存同步延迟“。
volatile能保证可见性,却不保证有序性,而synchronized能保证三种特性,但是对性能有影响。(PS:volatile是否保证有序性有一定的争议,参考其他文章后认为其不能保证)
先行发生原则:
程序次序规则(Program Order Rule)、管程锁定规则(Monitor Lock Rule)、volatile变量规则(Volatile Variable Rule)、线程启动规则(Thread Start Rule)、线程终止规则(Thread Termination Rule)、线程中断规则(Thread Interruption Rule)、对象终结规则(Finalizer Rule)、传递性(Transitivity)
先行发生原则是判断数据是否存在竞争,线程是否安全的主要依据。
先行发生是Java内存模型中定义的两项操作之间的偏序关系,如果说操作A先行发生于操作B,也就是说发生操作B之前,操作A产生的影响能被操作B观察到。
时间上的先后顺序和先行发生原则之间基本没有太大关系。
线程的实现:
线程是比进程更轻量级的调度执行单位,各个线程可以共享进程的资源,又可以独立调度(线程是CPU调度的最基本单位)。
实现线程主要有三种方式:使用内核线程实现,使用用户线程实现,使用用户线程加轻量级进程混合实现。
使用内核线程实现:
直接由操作系统内核(多线程内核)支持的线程,线程切换由内核来完成,内核通操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。
程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程——每个轻量级进程都有都由一个内核线程支持。
缺陷:系统调用的代价相对较高(需要在用户态和内核态中来回切换)、消耗内核资源(如内核线程栈空间)=>一个系统支持轻量级进程的数量是有限的。
使用用户线程实现:
完全建立在用户空间的线程库上,系统内核不能感知到线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。
基本不需要切换到内核态,因此操作可以是非常快速且低消耗的。
没有系统内核的支援,所有的线程操作都需要用户程序自己处理,比较复杂。
使用用户线程加轻量级进程混合实现:
既存在用户线程,也存在请练级进程。用户线程可以支持大规模的用户线程并发,而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁。
混合模式下,用户线程和轻量级进程的数量比是不定的。
Java线程的实现:
jdk1.2之前使用的是用户线程,之后的版本线程模型与平台相关。
主流的操作系统都提供了线程实现,Java语言则提供了在不同硬件和操作系统平台下对线程的统一处理,每个java.lang.Thread类的实例就代表了一个线程。
Thread类的所有关键方法都被声明为Native,意味着这些方法没有使用或无法使用平台无关的手段来实现,或者为了执行效率而使用Native方法。
对于Sun JDK来说,它的windows版和linux版都是使用一对一的线程模型来实现的,一条Java线程映射到一条轻量级进程之中。
Java线程调度:
线程调度是指系统为线程分配处理器使用权的过程,主要有两种调度方式:协同式线程调度(Cooperation Threads-Scheduling)、抢占式线程调度(Preemptive Threads-Scheduling)。
协同式线程调度:
线程执行时间由线程本身来控制,线程执行完成后,要主动通知系统切换到另一个线程上去。
实现简单,由于线程切换操作对线程自己是可知的,所以没有什么线程同步的问题。
线程执行时间不可控制,如果一直不通知系统进行线程切换,可能导致整个程序阻塞。
抢占式线程调度(目前Java使用的调度方式):
每个线程将由系统分配执行时间,线程的切换不由线程本身来决定(Java中线程可以提前让出,但不可以强制获取到执行时间)。
线程的执行时间是系统可控的,不会出现一个线程导致整个进程阻塞的问题。
通过设置线程的优先级,可以建议系统给线程分配的时间不同。线程优先级高的容易被系统选择执行,但不保证一定优先选择执行。
Java线程状态转换:
Java语言定义了5种线程状态,在任意一个时间点,一个线程只能有且只有其中的一种状态:新建(New)、运行(Runable)、无限期等待(Waiting)、限期等待(Timed Waiting)、阻塞(Blocked)、结束(Terminated)。
- Android群英传笔记——第十二章:Android5.X 新特性详解,Material Design UI的新体验
Android群英传笔记--第十二章:Android5.X 新特性详解,Material Design UI的新体验 第十一章为什么不写,因为我很早之前就已经写过了,有需要的可以去看 Android高 ...
- “全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- [CSAPP笔记][第十二章并发编程]
第十二章 并发编程 如果逻辑控制流在时间上是重叠,那么它们就是并发的(concurrent).这种常见的现象称为并发(concurrency). 硬件异常处理程序,进程和Unix信号处理程序都是大家熟 ...
- o'Reill的SVG精髓(第二版)学习笔记——第十二章
第十二章 SVG动画 12.1动画基础 SVG的动画特性基于万维网联盟的“同步多媒体集成语言”(SMIL)规范(http://www.w3.org/TR/SMIL3). 在这个动画系统中,我们可以指定 ...
- Java虚拟机学习(1):体系结构 内存模型
一:Java技术体系模块图 Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代" ."非堆", 它用于存储虚拟机加载的类信息.常量.静态 ...
- 【Java学习笔记之三十二】浅谈Java中throw与throws的用法及异常抛出处理机制剖析
异常处理机制 异常处理是对可能出现的异常进行处理,以防止程序遇到异常时被卡死,处于一直等待,或死循环. 异常有两个过程,一个是抛出异常:一个是捕捉异常. 抛出异常 抛出异常有三种形式,一是throw, ...
- Java学习笔记(十六)——Java RMI
[前面的话] 最近过的好舒服,每天过的感觉很充实,一些生活和工作的技巧注意了就会发现,其实生活也是可以过的如此的有滋有味,满足现在的状况,并且感觉很幸福. 学习java RMI的原因是最近在使用dub ...
- 第十二章 Java内存模型与线程
Java内存模型(Java Memory Model,JMM): 主内存与工作内存:Java内存模型主要是定义程序中各个变量的访问规则.Java内存模型规定了所有的变量都存储在主内存(Main Mem ...
- Linux内核设计与实现 总结笔记(第十二章)内存管理
内核里的内存分配不像其他地方分配内存那么容易,内核的内存分配不能简单便捷的使用,分配机制也不能太复杂. 一.页 内核把页作为内存管理的基本单位,尽管处理器最小寻址坑是是字或者字节.但是内存管理单元MM ...
随机推荐
- 【JavaScript&jQuery】购物车自动结算
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- xpose修改手机imei码,注入广告
何为hook Hook英文翻译过来就是“钩子”的意思,那我们在什么时候使用这个“钩子”呢? 我们知道,在Android操作系统中系统维护着自己的一套事件分发机制.应用程序,包括应用触发事件和后台逻 ...
- BZOJ 1070 修车 【费用流】
Description 同一时刻有N位车主带着他们的爱车来到了汽车维修中心.维修中心共有M位技术人员,不同的技术人员对不同 的车进行维修所用的时间是不同的.现在需要安排这M位技术人员所维修的车及顺序, ...
- codeforces765F Souvenirs
本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...
- 【bzoj4811】由乃的OJ
Portal --> bzoj4811 Solution 这题可以用树剖+线段树做也可以用LCT做,不过大体思路是一样的 (接下来先讲的是树剖+线段树的做法,再提LCT的做法) 首先位 ...
- 基于线程池技术的web服务器
前言:首先简单模拟一个场景,前端有一个输入框,有一个按钮,点击这个按钮可以实现搜索输入框中的相关的文本和图片(类似于百度.谷歌搜索).看似一个简单的功能,后端处理也不难,前端发起一个请求,后端接受到这 ...
- 省选模拟赛 LYK loves graph(graph)
题目描述 LYK喜欢花花绿绿的图片,有一天它得到了一张彩色图片,这张图片可以看做是一张n*m的网格图,每个格子都有一种颜色去染着,我们用-1至n*m-1来表示一个格子的颜色.特别地,-1代表这个颜色是 ...
- js 多个事件的绑定及移除(包括原生写法和 jquery 写法)
需要打开控制台查看效果: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q ...
- Java基础-synchronized关键字的用法(转载)
synchronized--同步 顾名思义是用于同步互斥的作用的. 这里精简的记一下它的使用方法以及意义: 当synchronized修饰 this或者非静态方法或者是一个实例的时候,所同步的锁是加在 ...
- JVM调优总结:一些概念
数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型.基本类型的变量保存原始值,即:他代表的值就是数值本身:而引用类型的变量保存引用值.“引用值”代表了某个对象的引用,而不是对象本身, ...