多线程进行任务处理在现代的计算机系统中是一个基本功能了,而且能够同时对多少个客户端提供相应是衡量一个服务性能的重要指标之一,例如每秒事务处理书 TPS,说明这一秒内服务端响应的请求综合能力,对于计算量相同的任务,程序线程并发协调越有条不紊,效率会越高,线程频繁阻塞甚至死锁,会降低程序的并发能力,

Java语言和虚拟机提供了很多的工具,将并发编程的门槛降低了不少

这次就通过java的内存模型来进行查看Java如何实现高并发的

在Java虚拟机中,试图定义一种Java内存模型,来屏蔽掉硬件和操作系统方向上的不同,Java内存模型规定了所有变量都存储在主内存中,而且每个工作线程中还有这自己的工作内存,和前面讲的处理器高速缓存类比,线程的工作内存中欧冠保存着线程使用的变量的主内存副本拷贝,线程对变量所有的操作,都必须在工作内存中进行,然后将更改的变量值传递给主内存完成更改,

图片

上面来说,主内存对应着Java堆中的对象实例数据部分,工作内存对应虚拟机栈中的部分区域,更低层第上说,主内存就是对应物理硬件的内存,

在虚拟机中,会让工作内存优先存储在寄存器和高速缓存中,因为程序运行时候主要访问的是工作内存

内存间的交互操作

关于主内存和工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存,如何从工作内存同步回主内存的细节,Java定义了几种操作

1.lock(锁定) 用于主内存的变量,将变量标识味一个线程独占的状态

2.解锁(解锁) 用于主内存的变量,将处于锁定的变量释放出来

3.read(读取) 作用于主内存的变量,彼将主内存传输到工作内存中,方便load动作的使用

4.load(载入)用于工作内存的变量,将read操作从主内存中放入到工作内存中

5.use(使用) 工作内存的变量,将工作内存中的变量给执行引擎

6.assign(赋值) 作用于工作内存的变量,将一个执行引擎接收到的值赋给工作内存的变量

7.store(存储) 用于工作内存的变量,将工作内存中的变量给主内存中,方便后面write操作

8.write(写入) 作用于主内存的变量,将store操作从工作内存中得到的变量放到主内存的变量中

对于上面的操作,要求 read和load必须顺序的执行 将主内存复制到工作内存

store和write操作必须可以按顺序执行

只有这两种类型的操作要求是按照顺序执行,其他并不要求,store和write之间可以插入其他的指令,而且还附带这有其他的规则

1.不允许read和load store和write操作单独出现,不能有一个变量从主内存读出但是工作内存没有接受,不能工作内存发起回写了但是主内存不接受

2.不允许线程丢弃他最近的assign操作,也就是指要在工作内存中改变过了,就必须同步回去给主内存

3.一个新的变量必须在主内存中诞生,不能在工作内存中直接使用一个未被初始化的变量

4.一个变量在同一时间只能有一条线程进行lock操作,但是lock操作可以被同一条线程执行多次,执行多少次就有多少次的unlock操作,变量进行解锁

5.如果对一个变量进行lock,会清空这个工作内变量的值,在执行引擎执行这个变量之前,先重新执行load和assign操作初始化变量的值

6.如果一个变量实现没有被lock锁定,就没法执行unlock操作

7.变量执行unlock操作之前,必须先把变量同步回主内存

8.不能无原因的将数据从线程的工作内存同步回主内存

上面的8条规则确定了Java程序在内存访问操作下的安全性,实践起来相当的繁琐,后来可以说是先行发生原则

体现在Java volatile型变量的特殊规则,关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制,很多程序员不会使用其,而且Java为其提供了一些特殊的特性

当一个变量被定义为volatile时候,会具有两种也行,保证这个变量对所有线程的可见性,也就是有一个线程更新了这个变量,所有的变量都能立刻得到,普通变量的值需要在主内存中传递

但是,是也只是在工作内存中可恶意立刻看见,在执行的引擎之中,还是可能有不一致的情况,也就是不绝对的一致性

只要变量只要不符合下面两个中的任何一个规则,就必须要采用某些特殊方法来保证其的原子性问题

1.运算结果并不依赖变量的当前值,或者确保只有单一线程修改变量的值

2.变量不需要和其他的状态变量共同参与不变约束

其次voliatile变量可以禁止指令进行重排序优化,普通的变量在编译优化之后,可以保证在其运行时候的运算过程可以正常走下去,但是不会保证操作的顺序和程序代码中的执行顺序一致,

所以volatile其禁止了重排序的优化,保证了

从内部来说,加入了volatile之后,就相当于加了一个内存的屏障,而指令重排序的时候,是无法越过这个内存屏障的

虽然其对于隔离性的保证并没有锁那么好,但是只要能使用volatile还是需要尽量的使用volatile的

然后总结一下volatile的特殊规则,如果有两个volatile变量的时候 w v的时候,进行read load use assign store write操作时候可以满足

1.必须要加载进入工作内存后立刻使用

2.必须在修改后立刻放入主内存中

3.对w的加载和读入优于v的加载和读入的话,w的写回必然也优于v

其次是关于long和double型变量的特殊规则

Java内存模型要求上面所说的8红操作都必须具有原子性,但是64位的数据类型已经超过了单一字节了,所以虚拟机允许没有被valatile修饰的64位的数据读写分为了两次32位的操作来进行

选择性的不保证 load store read write这4个操作的原子性,也就是所谓的long和double的非原子性协定

所以多个线程对一个非volatile的long或者double类型的变量进行读写的时候,可能拿到一个非原值,也非修改到一半的值

但是伴随着虚拟机的发展,这种读一半的操作已经不会再出现了

原子性 可见性 有序性

原子性

由于对于变量的操作包括了read load assign use store write

可以认为基本的读写是具有原子性的,而且如果需要更大范围去保证一个原子性,还可以使用monitorenter 和monitorexit 来进行保证原子性,体现在代码中就是synchronized关键字

可见性 一个线程修改额共享变量,其他的线程可以立刻知道,

除了volatile可以有效的保证可见性,其实其也是建立在8个基础操作之上的,Java还可以利用synchronized和final来保证可见性,对一个变量执行unloack操作之前,必须将变量同步到主内存中,final关键字的可见性可指,被初始化之后,可以再其他线程中直接访问,反正也无法修改

有序性

Java内存模型的有序性在之前探讨了,如果在本线程内部关键,操作都是有序的,一个线程观察其他线程,操作是无序的,前半句是指线程内表现为串行的语义,后半句是指 指令重排序和工作线程和主内存同步延迟的现象

Java语言提供了volatile和synchronized保证有序性

发表评论

邮箱地址不会被公开。 必填项已用*标注