码迷,mamicode.com
首页 > 编程语言 > 详细

多线程-Java内存模型与线程

时间:2017-10-18 12:30:30      阅读:251      评论:0      收藏:0      [点我收藏+]

标签:win   分配   性问题   对象   work   元素   编码   多线程   因此   

概述

为了解决内存与cpu之间的速度矛盾,在两者之前引入了写速度尽可能接近cpu运算速度的高速缓存:将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理就无须等待缓慢的内存读写了。

但是这也为计算机系统带来更高的复杂度,因为它引入了一个新的问题:缓存一致性。在多处理系统中,每个处理都有自己的高速缓存,而它们又共享同一主内存,当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致。

技术分享

内存模型:可以理解为在特定的操作协议下,对特定的内存和高速缓存进行读写访问的过程抽象。不同架构的物理机器可以拥有不一样的内存模型,而Java虚拟机也有自己的内存模型。

Java内存模型(Java Memory Model,JMM)从jdk1.2之后建立起来并在jdk1.5中完备过的内存模型。Java虚拟机规范中试图定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台都能达到一致的内存访问效果。C、C++等直接使用武力硬件和操作系统的内存模型,会由于不同平台内存模型的差异,导致程序无法完美的移植。

主内存和工作内存

Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节。 这里的比那里与Java编程中所说的变量有所区别,它包括了实例字段,静态字段和构成数组对象的元素,但不包括局部变量和方法参数(线程私有的,不会被共享,不存在竞争)

Java内存模型规定了所有的变量都存储在主内存(Main Memory)中。每条线程还有自己的工作内存(Working Memory),工作内存中保存了被该线程使用的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程见变量值的传递均需要通过主内存来完成。

技术分享

这里的主内存和工作内存与Java内存区域中的Java堆,栈,方法区等不适同一个层次的内存划分。

主内存和工作内存之间具体的交互协议:一个变量如何从主内存拷贝到工作内存,如果从工作内存同步回主内存等的实现细节。

Java内存模型定了8中操作来完成,虚拟机必须保证每一种操作都是原子的,不可再分的:

(1)lock:锁定,作用于主内存的变量,它把一个变量标识为一条线程独占的状态;

(2)unlock:解锁,作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;

(3)read:读取,作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;

(4)load:载入,作用于工作内存的变量,它把read操作从内存中得到的变量值放入工作内存的变量副本中;

(5)use:使用,作用于工作内存中的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值得字节码指令时将会执行这个操作;

(6)assign:赋值,作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作;

(7)store:存储,作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用;

(8)write:写入,作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

如果要把一个变量从主内存复制到工作内存,那就要顺序执行read和load操作,如果把变量从工作内存同步回主内存,就要顺序地执行store和write操作。

Java内存模型还固定了在执行8种基本操作时必须满足的规则:

(1)不允许read和load,store和write操作之一单独出现;

(2)不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变之后必须把该变化同步回主内存;

(3)不允许一个线程无原因的(没有发生过任何assign操作)把数据从工作内存同步回主内存中;

(4)一个新变量只能在主内存中“诞生”;

(5)一个变量在同一个时刻只允许一条线程对其进行lock操作,并且可以被同一条线程重复执行多次;

(6)多一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值;

(7)如果一个变量事先没有被lock锁定,就不允许对他执行unlock,也不允许去unlock一个被其他线程锁定的变量;

(8)对一个变量执行unlock操作之前,必须先把此变量同步回主内存中

 volatile

volatile具备两种特性:(1)保证此变量对所有线程可见性(2)禁止指令重排序优化。

volatile变量在规格线程中的工作内存中不存在一致性的问题:在规格线程的工作内存中,volatile变量也可以存在不一致的情况,但由于每次使用之前都要先刷新,执行引擎看不到不一致的情况,因此可以认为不存在不一致性问题。但是Java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的。

原子性,可见性与有序性

Java内存模型是围绕着在并发过程中如何处理原子性,可见性和有序性这3个特征来建立的

(1)原子性:Java内存模型来直接保证的原子性变量操作包括read,load,assign,use,store,write;

lock和unlock操作对应的字节码指令是monitorenter和monitorexit,反应到Java代码中就是同步块--synchronized关键字,因此在synchronized块之间的操作也具备原子性。

(2)可见性:当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是:volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此,可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。

(3)有序性:Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性。

先行发生原则

(1)程序次序规则

(2)管程锁定规则

(3)volatile变量规则

(4)线程启动规则

(5)线程终止规则

(6)线程中断规则

(7)对象终结规则

(8)传递性

一个操作“时间上的先发生”不代表这个操作会是“先行发生”,一个操作是“先行发生”也未必是“时间上的先发生”。

衡量并发安全问题的时候不要受到时间顺序的干扰,一切必须以先行发生原则为准。

线程的实现

主要有3种方式:(1)使用内核线程实现(2)使用用户线程实现(3)使用用户线程加轻量级进程混合实现

(1)内核线程:就是直接由操作系统(kernel,内核)支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到规格处理器上。一般不会直接去使用内核线程,而是使用内核线程的一种高级接口--轻量级进程。

(2)用户线程:广义上,一个线程只要不是内核线程,就可以认为是用户线程,从这个定义上来说,轻量级进程也数据用户线程。狭义上,用户线程是指完全建立在用户控件的线程库上,系统内核不能感知线程存在的实现。

(3)用户线程加轻量级进程混合:即前两种的混合。在这种混合实现下,即存在用户线程,也存在轻量级进程,用户线程还是完全建立在用户空间中,因此用户线程的创建,切换,析构等操作依然廉价,并且可以支持大规模的用户线程并发。而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级进程来完成,大大降低了整个进程被完全阻塞的风险。

Java线程实现

(1)实现:从jdk1.2之后,线程模型替换为基于操作系统原生线程模型来实现的。操作系统支持怎么样的线程模型,在很大程度上决定了Java虚拟机线程是怎样映射的,这点在不同的平台上没有办法达成一致,虚拟机规范中并未限定Java线程需要使用哪种线程模型来实现。线程模型只对线程的并发规模和操作成本产生影响,对Java程序的编码和运行过程来说,这些差异都是透明的。

Sun JDK它的Windows和Linux版本都是使用一对一的线程模型实现的,一条Java线程就映射到一条轻量级进程之中。

(2)指系统为线程分配处理器使用权的过程,主要调度方式有两种:(A)协同式线程调度(B)抢占式线程调度;Java使用的调度方式就是抢占式调度。

(3)装填转换

  1. 新建:创建后尚未启动的线程
  2. 运行:处于此状态的线程有可能正在执行,也有可能正在等待着cpu为它分配执行时间
  3. 无限等待:不会被分配cpu执行时间,要等待被其他线程显式地唤醒,如Object.wait(),Thread.join(),LockSupport.park()
  4. 有限等待:超时可由系统自动唤醒。如Thread.sleep(),Object.wait(long),Thread.join(long),LockSupport.parkNanos(),LockSupport.parkUntil()
  5. 阻塞:阻塞状态是指在等待着获取到一个排他锁;等待状态是指在等待一段时间或唤醒动作的发生。
  6. 结束:已终止。

线程安全

定义:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用整个对象的行为都可以获得正确的结果,那整个对象是线程安全的。

线程安全的实现方法

 

 

 

 

 

 

 

 

 参考资料

深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)

多线程-Java内存模型与线程

标签:win   分配   性问题   对象   work   元素   编码   多线程   因此   

原文地址:http://www.cnblogs.com/lujiango/p/7685065.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!