深入理解java虚拟机----java内存模型与线程

12.1  概述

衡量一个服务性能的高低好坏,每秒事务处理数是最重要的指标之一,它 着一秒内服务端平均能响应的请求总数,而TPS的与程序的并发能力又有非常密切的关系。

12.2 硬件的效率与一致性

因为有缓存一致性,所以要有一些操作来保证安全。

12.3 java内存模型

java虚拟机规范中试图定义一种java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的内存访问效果。

定义java内存模型并非一件容易的事情,这个模型必须定义得足够严谨,才能让java的并发内存访问操作不会产生歧义;但是,也必须定义得足够宽松,使得虚拟机的实现有足够的自由空间去利用硬件的各种特性来获取更好的执行速度。

12.3.1 主内存与工作内存

直接看上面的图吧 java内存模型规定了所有的变量都存储在主内存中。每条线程都有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

12.3.2内存间交互操作

内存间的交互动作,即工作内存与主内存之间的拷贝同步,Java规定下述8中操作完成两者之间的拷贝与同步,并保证是原子性的(long、double例外),注意,read和load、store和write是顺序执行的,但不代表指令是连续执行的:

          1、锁定lock,作用于主内存变量,将其标识为线程独占

          2、解锁unlock,作用于主内存,从锁定状态解锁,释放后的变量才能被其他线程锁定

          3、读取read,作用于主内存,从主内存传递到工作内存,以便load

          4、加载load,作用于工作内存,将read读回的变量值放入工作内存的变量副本中

          5、使用use,作用于工作内存,将工作内存的变量值传递给执行引擎,虚拟机在遇到执行引擎需要使用某个变量时会执行这个操作

          6、赋值assign,作用于工作内存,将从执行引擎收到的值赋给工作内存中变量,虚拟机遇到给变量赋值的字节码时会执行这个操作

          7、存储store,作用于工作内存,将本地内存的值传递给主内存,以便write

          8、写入write,作用于工作内存,将从store操作从工作内存中得到的变量值写回主内存变量中

上述8中操作还必须满足下列条件:

          1、不允许read和load、store和write之一出现,即必须是成对出现

          2、不允许线程丢弃最近的assign操作,即变量在工作内存中的改变必须写回主内存

          3、不允许线程无缘故(没有assign操作)地将数据从工作内存写回主内存

          4、一个新变量只能在主内存中产生,不允许在工作内存中直接使用一个没有初始化的变量,即use、store之前,必须执行过load、assign

          5、一个变量只允许被同一个线程lock,允许多次lock,lock次数等于unlock的次数

          6、对一个变量执行lock,将会清空工作内存中此变量的值,执行引擎使用改变量之前,需要重新load或assign操作以初始化改值

          7、不允许对一个没有lock的变量进行unlock操作,也不允许一个线程unlock另一个线程锁住的变量

          8、对变量执行unlock之前,必须将变量同步会主内存

12.3.3 对于volatile型变量的特殊规则

volatile类型,轻量级的同步机制,具有如下两种特性

          1、保证此变量对所有线程的可见性,即某个线程改变了这个变量的值,新值对于其他线程来说是立即可见的,volatile只保证可见性,在不满足下列情况时,仍需要使用同步

                    1、计算结果不依赖当前值,或者保证只有一个线程可以改变这个值

                    2、变量不需要其他状态变量共同参与不变约束,即同步更新问题

          2、volatile禁止语义重排

12.3.4 对long和double型变量的特殊规则

long、double的特殊性,虽然虚拟机允许将long、double这些64位的读写操作划分为两个32为操作,即在多线程情况下可能出现读到“半个值”的情况,但实际开发中,商用虚拟机都是将其实现为院子操作,因此这两类类型在开发时不必特别声明为volatile

Java内存模型主要围绕如何实现原子性、可见性、有序性特征建立:

          1、原子性,read、load、use、assign、store、write保证了基本变量的原子性,对于更大范围的原子性,可以图通过使用synchronized实现

          2、可见性,Java通过在变量修改后将新值同步回住内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式实现可见性,无论是否使用volatile关键字,而普通变量与volatile修饰的变量区别在于volatile保证新值能立即同步到主内存,每次使用前立即从主内存刷新

          3、有序性,如果从线程内部观察,所有操作串行执行,而从线程之间观察,操作是无序的(指令重排与工作内存和主内存同步延迟)

12.3.6 先行发生原则

如果操作A先行发生于操作B,即发生在操作B之前,那么操作A产生的影响能被操作B观察到,这里的影响包括修改共享变量的值、发送消息、调用方法等;通过先行发生原则,无须同步器协助,就能保证线程安全,如果两者不满足先行原则,并不能由如下规则导出,则处理器可以任意重排指定:

          1、程序次序原则,书写在前面的程序先于后面的程序发生,即线程中控制流的顺序

          2、管程锁定原则,对于同一个锁对象,unlock先行发生于后面对同一个锁的lock

          3、volatile变量原则,对一个volatile变量的写操作先行发生于读操作

          4、线程启动原则,Thread对象的start()先行与线程的每个动作

          5、线程终止原则,线程的所有操作先行与该线程的终止检测

          6、线程中断原则,对线程的interrupt()操作先行发生于被中断线程的代码检测到中断事件的发生

          7、对象终结原则,一个对象的初始化完成(构造函数执行结束)先行发生于finalize()方法的开始

          8、传递性,如果A先行于B,B先行于C,则A先行于C

注意,时间先后的顺序和先行原则发生之间没有太大关系,即并发安全问题必须以先行发生原则为准。

12.4 java与线程

12.4.1线程的实现

线程,是进程轻量级的调度执行单位,使得资源分配与线程调度分开,既可以共享资源,又可以独立调度(线程是CPU调度最小单位),线程的实现主要有三种方式:

          1、内核线程实现(kernel-level Thread),直接由操作系统内核支持的线程,由内核通过调度器完成线程调度、将线程映射到各个处理器中;每个内核线程可以看做内核的分身;程序一般使用内核线程的高级接口——轻量级进程LWT(即线程),LWT和内核线程是一对一关系;在这里,每个LWT就是调度的基本单位,局限性在于,线程所有操作由内核完成,而系统调用的代价较高,需要在内核态和用户态之间切换,并且LWT和内核线程是一对一的,因此LWT消耗内核资源,一个系统支持的LWT数量数有限的

          2、用户线程实现,狭义上,用户线程完全建立在用户空间的线程库上,内核不能感知线程的存在,线程的建立、同步、销毁完全在用户态完成,无须内核参与,这里进程与线程是一对多的关系;优势在于,不需要内核支援,操作快速低消耗;劣势在于,没有内核支援,用户进程需要考虑线程的所有操作,实现复杂,并且处理器资源只能分配到进程

          3、用户线程加内核线程混合实现,两者兼有,用户线程还是在用户空间,线程的创建、切换等依然高效,支持大规模并发;操作系统提高线程调度和处理器映射,轻量级进程作为用户线程和内核线程的桥梁,降低了整个进程被阻塞的风险;这里用户线程和轻量级线程之间关系是多对多;

Java线程实现:1.2及以后,线程模型为基于操作系统原生线程模型来实现,即操作系统支持什么线程模型,决定了虚拟机支持什么线程模型。

12.4.2 java线程调度

Java线程调度:线程调度是指为线程分配处理器使用权限的过程;主要分为两种方式:

          1、协作式线程调度,线程执行时间由线程自身控制,执行完成后通知系统切换到另一个线程;优点是实现简单,不存在线程安全性问题,缺点是线程执行时间不可控。

          2、抢占式线程调度,线程执行由系统分配时间,线程的切换不是线程自身决定;优点是执行时间可控,不会出现一个线程导致整个进程阻塞问题;Java使用的就是这种线程调度方式。

Java线程优先级,java虽然设置了10个优先级,但是Java的线程是通过映射到系统原生线程上来实现的,线程的调度取决于系统,系统优先级和Java优先级不一定一一对应,并且优先级还可能被系统改变

12.4.3 状态转换

Java线程状态,共五种状态,一个线程在任意时间点,有且仅有其中一种状态:

          1、新建new,创建尚未启动

          2、运行runnable,包括操作系统的running和ready状态,可能正在执行,可也能在等待CPU时间

          3、无限期等待waiting,不会分配CPU时间,必须由其他线程显式唤醒,如未设置时间的wait()、join();发生在线程进入同步区

          4、限期等待timed waiting,不会被分配CPU时间,但是无须其他线程显式唤醒,超时将右系统自动唤醒,如设置时间的wait()、join();发生在线程进入同步区

          5、阻塞blocked,线程在等待一个排它锁,事件将在一个线程放弃锁的时候发生

          6、结束terminated,终止的线程,线程结束执行

12.5  本章小结

本章中,我们首先了解了虚拟机java内存模型的结构及操作,然后讲解了原子性、可见性、有序性在java内存模型中的体现,最后介绍了先行发生原则的规则及使用。另外,我们还了解了线程在java语言之中是如何实现的。

猜你喜欢

转载自blog.csdn.net/qq_40182703/article/details/81290780
今日推荐