Java复习笔记——java内存模型和线程

java内存模型和线程

 

1、概述

衡量一个服务的好坏,可以通过使用美妙事务处理数(TPS)来衡量。

2java 内存模型

这里的java内存模型和前边的jvm的内存模型不同,这里是的内存模型是java通过一个标准的模型去消除不同平台之间的硬件的差异。所以这里的内存模式是和计算机的内存模型类似的。

java的内存模型主要是定义了各个变量的访问规则,这里的规则主要是虚拟机内存存储和读取变量的底层细节。这里的内存并不是java语法上的内存,而是实例字段,静态字段和构成数组对象的元素,并不包括局部变量和方法的参数,因为后者是线程私有的(因为不是共享数据)。

一个java线程会有自己的工作内存,这些工作内存类似于计算机中的高速缓存,通过saveload操作与jvm中的主内存进行交互。

 

3、内存之间的交互

工作内存是相互独立的,但是主内存是所有线程共享的,在线程进行运算的时候就这两者就需要进行数据的交互

由于实现虚拟机的时候就需要保证8中操作的原子性:lockunlockreadloaduseassignstorewrite

lockunlock就是线程的锁定和解锁。剩下的就是主内存和工作内存之间的交互了,这两者的主要的数据交互如下:

数据会被加载到主内存中,主内存会先进行read操作,将数据交给工作内存,同时,工作内存会进行load操作,将从主内存里边获取到的数据变量值放到工作内存的变量副本中(Treadlocal?)。然后通过use操作,把工作内存中的变量执行引擎处理。处理结束之后,通过assign获取到处理之后的数据,并存储到工作内存中。然后通过store将数据从工作内存传递到主内存中。最后主内存通过write将工作内存传递的数据变量放到主内存的变量中。

由于这个规则的存在,就导致上边的一些操作是要成对存在的,例如readwriteloadstore等。而且也存在一些先后的顺序,如必须先read才能load等。

 

4volatile

volatilejvm中最轻量级的同步机制,使用volatile在读的时候和普通变量没有区别,只会在写的时候慢一些。

volatile具备两个特性,一个是保证此变量在被修改的时候对所有线程的可见性,另一个就是禁止指令重排序优化。

这两点看似独立,但其实是一个点——内存屏障。

通过源码可以发现,被volatile修饰的时候,字节码文件会多出一行代码:

XXXXX:lock addl XXXX

这个是汇编语言,X86架构里边lock表示锁定,而前边的一窜是CPU的指令,通过查询得出这个指令是会将本CPUcache写入内存, 这个写入动作,会让其他CPUcache无效化。这一个操作就相当于前边所说的storewrite操作。

这个操作会带来两个影响,一个就是每一个CPU都需要去重新获取工作内存中的数据,也就会读取到最新的数据,也就是在其中一个线程中的更改可以立刻让其他线程获取。 另外一个就是保证了这个更改之前的操作都会存到内存中,也就是前边的操作已经不可逆了。所以在volatile之前的操作一定会先于volatile之后的操作执行。这样就表现成了重排序失败的情景。

5longdouble的特殊规则

在默认情况下,由于longdouble都是64位(bit)的,所以jvm会默认将这些数据的操作分开两次进行。但是在jvm实现的时候,各个商业虚拟机都已经对其进行了原子性的处理,所以不需要特殊考虑。

6、原子性、可见性、有序性

对于变量的操作来说,是有三个特征影响线程的处理。

原子性。由于java内存模型的8个操作,所以我们可以任务基本的数据类型的操作都是原子性的。即使是对象,也可以通过lockunlock去实现操作的原子性。虽然这两个指令虚拟机并没有直接开放给用户使用,但是有更高层次的monitorenter(监视入口)和monitorexit(监视出口),这两个字节码指令反应到代码中就是Synchronized。所以被Synchronized修饰的操作都有原子性。

可见性。也就是在一个线程中修改了一个变量之后,其他线程都立刻可以感知。根据上班的叙述可知,volatile可以实现这点。除了volatile之外,Synchronizedfinal都可以实现可见性。Synchronized由于unlock操作在进行之前必须把数据写回到内存中,所以可以实现可见性。final因为在初始化之后,构造器并没有把this传递出去。这样就没有人可以再次对这个对象/变量进行赋值。

有序性java程序中天然的有序性可以总结为:在本线程内观察,线程内部的所有操作都是有序的,但是其他线程的操作都是无序的。这是由于jvm在编译的时候会就对代码进行重新排序,而且各个工作内存中的数据可能由于读取主内存的时间不一致,导致数据不一致。

java中保证有序性的关键字除了前边叙述的volatile,还有Synchronizedvolatile可以理解,因为本身机会出发一个lock操作,导致其他CPU中的cache无效化。而Synchronized则是通过锁来保证的,因为同一个时间之内获取锁的只有一个线程,所以当多个线程同时获取锁的时候,就会造成阻塞。这个阻塞,同时也是一种排队。

7、先行发生原则。

这个是java里边一个重要的原则。

如果A操作先行与B操作,那么A产生的影响就会被B感知到。

例如:

//A
i = 1;

//B
j = i;

// C
i = 2;

由先行发生原则可以知道B操作在A操作之后,所以B操作一定可以感知到A操作。但是如果这个时候有另外一个并行的线程也在处理这个数据,刚好就在AB之间进行了C的操作,那么B就有可能读到2。这样B的操作就是不安全的。

 

java与线程

8java中线程的实现

线程是CPU调度的基本单元。已经执行start()但是还没有结束的javaThread类对象就可以认为是一个线程。程序实现线程有三种模式:内核模式、用户线程模式、混合模式

内核模式就是直接由计算机系统的内核进行操作, 程序一般不会去使用线程内核,会使用线程内核的一种高级接口——轻量级进程。轻量级进程和内核线程之间的关系是一一对应的。

这个模式的好处就是在于无需对线程之间的调度进行控制,完全交给CPU处理。而缺点也正是因为所有的调度都需要由CPU去处理,所以CPU需要在用户态和内核态中经常切换。每一次切换都是非常耗费资源的。

使用用户线程实现。一个线程只要不是内核线程就可以被认为是用户线程。使用用户线程,就不需要去进行状态的消耗,而且是支持大规模数量的线程的。缺点就是所有对线程的操作都需要由用户去决定。而会有一些很难处理的问题出现,例如:阻塞的时候怎么处理,怎么分配线程资源等。

混合模式也就是在用户线程上增加了轻量级进程的映射,也就是同时存在用户线程和轻量级进程的情况。由于使用的是用户线程 ,所以在操作现成的时候是比较方便的,而线程的调度是通过轻量级线程进行的,这样会比较轻松。它们之间的关系编程了N:M

SunJDK是将线程和轻量级进程一一映射的。

9、线程之间的调度

线程之间的调度有两种,协同式调度,抢占式调度。

协同式调度是指线程的执行时间由线程本身控制,线程把自己的工作执行结束了之后就主动通知系统切换。缺点是一个线程如果陷入死循环,那么这个线程机会一直等待。

抢占式调度,就是由系统分配线程的执行时间,这样线程的执行时间就是可以控制的。这里边虽然可以对不同的线程分配优先级,但是这种优先级是不可靠的,因为不同的系统对优先级的分配不相同,所以对应不上。

 

10、线程的状态切换

新建、运行、无限期等待、有限期等待、阻塞以及结束。

新建表示一个线程刚刚被启动,但是没有运行的状态。

运行表示线程状态中的RunningReady状态。这个时候可能被CPU分配执行时间,也可能么有。

无限期等待、有限期等待。都是没有被分配CPU执行时间的状态,却别是一个会自动唤醒,一个是等待别人唤醒。

阻塞。阻塞和等待的区别是阻塞会获得一个排它锁,这个锁在检测到另外的线程抛弃这个锁的时候就会发生。简单来说,阻塞就是这个线程正在等待别的线程抛弃锁。

结束。已经终止的线程状态,也就是这个线程结束了。

你可能感兴趣的:(Java)