Java内存模型

主内存与空间内存

Java内存模型的主要目的是定义程序当中各种变量访问规则,即关注在虚拟机中把变量存储到内存和从内存当做取出变量值这样的底层细节。此处的变量不包括局部变量和方法参数,因为后者是线程私有的。

Java内存模型当中规定了所有的变量都存储在主内存当中。每条线程还有自己的工作内存,线程的工作内存当中还保存了被该线程使用的变量的主内存副本(这个对象的引用,对象中某个在线程当中访问到的字段是有可能会被复制的,并不会复制全部内容)。线程对变量的多有操作都需在工作内存当中进行,而不能直接读写主内存当中的数据。

他们直接的关系是,Java线程直接对工作内存进行读写操作,然后工作内存完成后再对主内存进行同步(工作内存拷贝于主内存当中)。

也就是说内存当中的交互操作,Java内存模型规定了以下8种操作来完成:

  1. lock:作用于主内存的变量,它把一个变量表示为一条线程独占的状态。
  2. unlock:作用于主内存的变量,它将一个加锁的线程释放出来。
  3. read:作用于主内存的变量,他将一个变量的值传输到线程的工作内存当中供工作内存使用。
  4. load:作用于工作内存的变量,他把read操作从主内存拿出的数据放入工作内存的变量副本当中。
  5. use:作用于工作内存的变量,它将一个变量的值传输给执行引擎。
  6. assign:作用于工作内存的变量,它接受执行引擎的值传输并且赋给空间内存的变量。
  7. store:作用于工作内存的变量,将工作内存当中的数据传输给主内存。
  8. write:作用于主内存的变量,将store传输的数据写入工作内存。

对于Volatile型变量的特殊规则

一个变量被定义为Volatile类型后他会满足两个性质:1.第一项就是保持此变量对所有线程的可见性,这里的可见性指当一条线程修改了这个变量的值,新值对于其他线程是可以立即得知的。而普通变量是无法做到这一点的,同时Volatile变量的运算在并发下是不安全的,这是因为Java的操作运算符非原子性。2.他的第二个语义是进制指令重排列优化,普通变量仅仅会保证该方法在执行过程当中与程序代码执行结果正确,但是不能保证顺序的一致性。

重排列是机器级的优化操作,重排列后提前执行语句的汇编代码是有可能出现错误的,Volatile就能很好解决这点。

从硬件上讲,指令重排序是处理器采用了允许将多条指令不安程序规定分开发送给各个相应的电路单元进行处理。如果各个指令当中是存有依赖的,那么就不能进行重排序,比如说指令1是a+3 指令2是b+a,这种时候我们就可以认为1和2是有依赖的吗,这个时候就不能进行重排序。

Volatile类型在读操作上几乎与普通变量没有什么区别,但是写操作或许会慢一点,因为他需要插入许多内存屏障来确保不会发生重排序。

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

允许虚拟机将没有Volatile修饰的64位数据的读写操作划分为了两次32位的操作来进行,即为虚拟机自行选择是否对64位数据类型的变量进行原子操作。这就是所谓的“long和double的非原子性协定”。

Java与线程

线程的实现

实现线程主要有三种方法:1.内核线程实现(1:1)2. 使用用户线程实现 (1:N) 3.使用用户线程加轻量级进程进行混合实现(N:M)

内核实现

内核实现的方式也被称之为1:1实现。内核县城是直接由操作系统内核支持的线程,程序一般不会使用内核线程,而是使用内核线程的一种接口“轻量级进程”。也就是说一个内核支持一个轻量级进程的运行。每个轻量级进行都是一个独立的调度单元,即使其中某一个轻量级进程被阻塞了,也不会影响其他进程工作。

但是系统调用的代价过高,需要在用户态和内核态之间来回切换。

用户线程实现

用户线程就是完全建立在用户空间上的线程库里。系统内核不能感知用户线程的存在以及他是如何调用的。用户线程的建立,调用,同步,销毁完全在用户态中完成,不需要内核的帮助。

用户线程成也萧何,败也萧何。优势在于不需要系统内核的支援,劣势也在于没有系统内核的支援。用户线程通常用于被执行复杂的程序,若是没有明确的需求,一般不会使用用户线程。(复杂来源于特定的线程库,操作的复杂性被封装在线程库当中)

用户线程加轻量级进程进行混合实现

也就是将内核线程和用户线程混搭使用,用户线程还是完全建立在用户空间之上,操作仍然比较廉价,但是不同的是轻量级进程作为一个桥梁来连接用户线程,使得用户线程可以得到系统内核的支援。

Java的线程调度模式

线程调度模式有两种:1.协同式调度2.抢占式调度

  1. 协同式调度:线程的执行时间由自己来控制,线程将自己的任务执行完成后会主动通知系统切换到另外一个线程上去,好处在于实现简单,坏处在于时间不可控,要是这个线程代码有问题,就会一直阻塞下去。
  2. 抢占式调度:这也是Java使用的调度方式,若是一个线程出现了问题,任务管理器可以杀掉这个线程,不至于让系统崩溃。虽然说Java的先调度是自动完成的,但是我们仍然可以自己通过代码来建立操作系统给某些线程多分配一点时间。这可以通过设置线程的优先级来完成,Java一共设置了10个级别的优先级。两个线程去抢CPU,优先级高的抢到。但是不能完全保证是按照优先级来分配的,比如说在Windows系统当中有一个叫优先级推进器的功能,大致是你执行一个线程十分频繁的时候,他就会提升这个线程的优先级,也就是说越过优先级去执行这个线程,因为线程之间的频繁切换也会带来性能损坏。

锁优化

自旋锁

当一个持有锁的线程正在执行的时候,另外一个等待锁的线程为了不去浪费处理器的执行时间,可以让这个等待锁的线程进行忙循环(自旋),若是前面那个线程被占用的时间短,自旋锁的处理就会很好,如果时间太长就白白浪费了处理器资源。所以这里出现了自旋次数,若是超过了自旋次数,那么就会挂起等待线程。

锁粗化

锁粗化使用与在一个方法块当中频繁加锁导致加锁去锁十分耗费资源,这个时候就可以给整个代码块加锁。叫做锁粗化。

轻量级锁

在MarkWord写过。

偏向锁

偏向锁在执行过程,他会偏向于第一个获得他的线程,若是接下来该锁没有被其他线程获取,则持有锁的线程就不需要在进行同步。

一旦出现其他线程想去获取这个线程的行为,偏向模式立马结束,根据锁对象是否处于锁定的状态,来变为未锁定01状态和00轻量级锁定状态

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部