第一章:走进Java
第二章:Java内存区域与内存溢出异常
第三章:垃圾收集器与内存分配策略
第四章:虚拟机性能监控与故障处理
第五章:调优案例分析与实战
第六章:类文件结构
第七章:虚拟机类加载机制
第八章:虚拟机字节码执行引
第九章:类加载及其执行子系统的案例与实战
第十章:早期(编译器)优化
第十一章:晚期(运行期)优化
第十二章:Java内存模型与线程
第十三章:线程安全与锁优化
衡量一个服务端的好坏,每秒事物处理数(Transactions Per second,TPS)是最重要的指标之一
基于高速缓存的存储交互很好的解决了处理器与内存的速度矛盾,但是也为计算机系统带来了更高的复杂性,引入了新的问题:缓存一致性
为了使处理器内部运算单元尽量充分利用,处理器会对输入代码乱序执行优化
Java 虚拟机的即时编译器中也有类似的指令重排序优化
Java虚拟机规范定义了一种内存模型(JAVA Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现Java语言在各个平台都能达到一致的内存访问效果
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节,此处的变量和Java编程中所说的有所区别,它包括实例变量、静态字段、和构成数组对象的元素,但是不包括局部变量和方法参数,后者是线程私有的,不会被共享,自然就不会存在竞争问题
Java内存模型规定了所有的变量都存储在主内存(Main memory)中,每条线程还有自己的工作内存(Working Memory),线程的工作内存保存了被该线程使用的变量的主内存副本拷贝
线程对变量的操作(读取、赋值)必须都在工作内存中进行,而不能直接读写主内存中的变量
不同的线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递都通过主内存传递
关于主内存与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存,如何从工作内存同步到主内存,Java内存模型定义了8种操作来完成;每一种操作都是原子的、不可再分的(long和double有例外)
执行8中操作时候必须满足的规则
先行发生原则:用来确定一个访问在并发环境下是否安全
Java虚拟机提供的轻量级同步机制
作用:
不存在一致性问题
Java语言里的运算并非原子性,导致volatile变量的运算在并发下是安全的
在不符合以下两条规则的场景中,仍要通过加锁(使用synchronize或java.util.concurrent中的原子类)来保证原子性
运算结果并不依赖于变量的当前值,或者能够确保只有单一的线程修改变量的值
变量不需要与其他的状态变量共同参与不变约束
特殊规则
每次使用Volatitle变量前必须先从主内存刷新最新的值
工作内存中修改volatile变量后必须同步到主内存中
volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序和代码顺序一致
允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行,即允许虚拟机不保证64位数据类型的load、store、read和write这4个操作的原子性,这就是long和double的非原子性协定
目前商业虚拟机不会出现读取到“半个变量”的情况
原子性
可见性
当一个线程修改变量的值,其他线程能够立刻得知这个修改
synchronized、volatile、final都能保证可见性
有序性
如果在本线程内观察,所有的操作都是有序的,如果在一个线程中观察另一个线程,所有操作都是无序的
synchronized、volatile保证有序性
3种方式:
直接由操作系统内核支持的线程,由内核完成线程切换,内核通过操纵调度器进行线程调度,并且负责将线程的任务映射到各个处理器上
程序一般不会直接使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Light Weight Process ,LWP),轻量级进程就是我们通常意义上讲的线程
轻量级进程和内核线程之间1:1的关系称为一对一线程模型
轻量级进程的局限性
每个轻量级进程都需要一个内核线程支持,轻量级进程要消耗一定的内核资源,因此一个系统支持轻量级进程的数量是有限的
狭义的用户线程指的是完全建立在用户空间的线程库,系统内核不能感知线程存在
进程与线程之间的1:N的关系称为一对多的线程模型
优势在于不需要内核支援,劣势也在于没有内核支援
实现比较复杂,使用用户线程的程序越来越少
用户线程完全建立在用户空间,用户线程的创建、切换、析构的操作比较廉价,并且支持大规模的用户线程并发
操作系统提供支持的轻量级进程则作为用户线程和内核线程的桥梁
使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险
线程调度是指系统为线程分配处理器使用权的过程
两种主要调度方式:
执行时间由线程本身决定,执行完后,主动通知系统切换到另一个线程
好处是实现简单;切换操作对线程自己可知,所以没有什么同步问题;
坏处是执行时间不可控;可能会出现程序一直阻塞的情况
线程由系统分配执行时间,切换不由线程本身决定
执行时间系统可控,不会有一个线程导致整个进程阻塞的情况
Java使用的线程调度方式是抢占式线程调度
建议给系统分配时间:线程优先级
5种状态:
新建(New):创建后尚未启动
运行(Runable):包括操作系统线程状态中的Running和Ready,也就是正在执行和等待CPU为它分配执行时间的
无限期等待(Waiting):不会被CPU分配时间,要等待被其他线程显式地唤醒
限期等待(Timed Waiting):不会被CPU分配时间,一定时间后会自动唤醒
阻塞(Blocked):线程被阻塞
“阻塞状态”和“等待状态”的区别: