《深入理解Java虚拟机》读书笔记七

75 阅读7分钟

Java内存模型与线程

硬件效率与一致性

MSI、MESI、MOSI、Synapse、Firefly、Dragon

处理器的乱序执行与Java的指令重排

Java内存模型

java定义了自己的内存模型JMM,来屏蔽各种硬件与操作系统的内存访问差异

主内存与工作内存:

Java规定所有的变量都存储在主内存内

每条线程都有自己的工作内存,线程之间的变量值传递主要通过主内存完成

内存间交互操作

java中有八种协议:lock、unlock、read、load、use、assign、store、write

不允许read/load、store/write单独出现

不允许一个线程丢弃它的最近assign操作

不允许一个线程无原因的把数据从线程同步回主内存

一个新的变量只能从主内存诞生

一个变量在同一时刻只允许一条线程对齐lock

如果一个变量执行lock操作,将会清空工作内存中此变量的值

如果一个变量事先没有被lock锁定,不允许对它进行unlock,也不允许其他线程来unlock

一个变量进行unlock,必须同步回主内存

对于volatile变量特殊规则

被volatile修饰的变量,修改后其他线程立即可知(但不是代表就是原子操作)

使用volatile第二个语义是进制指令重排

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

如果多个线程共享一个为声明的long或double(由于64,虚拟机对写操作划分为两次执行),会出现错

但是大部分虚拟机已经优化,把64位当做原子操作

原子性、可见性与有序性

先行发生原则

Java与线程

线程的实现:使用内核线程实现、使用用户线程实现、使用用户线程加轻量级进程混合实现

1.使用内核线程实现

程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口-轻量级进程(LWP),一个系统支持的轻量级进程数量有限

 

2.使用用户线程实现

一个线程只要不是内核线程,就可以认为是用户线程(UT),因此这个定义上讲轻量级进程属于用户线程,实现困难后来java、ruby都放弃使用

但受操作系统调用,因此效率受限制

 

3.混合实现

操作系统提供支持轻量级进程作为用户线程和内很线程之间的桥梁

 

4.Java线程的实现

jdk1.2之前是基于”绿色线程“的用户线程实现、1.2后线程模型被替换为基于操作系统原生线程模型实现

java线程调度

协同式:

线程工作完之后通知系统切换到另外一个线程上去(Lua)

抢占式:

线程执行由系统控制(java),通过线程的优先级控制某些线程多一些时间,某些少点时间

Java语言设置了10个级别优先级,但是操作系统不一定支持这么多,就导致系统支持少的话就不确定了且会被系统自行改变

状态转换

新建:创建后尚未启动

运行:runnable包含操作系用户状态中的running和ready

无限期等待:要被其他线程显示唤醒(timeout)

限期等待:一定时间之后由系统自动唤醒(thread.sleep)

阻塞:和等待状态的区别是:等待获取到一个排它锁

结束:线程已经结束执行

 

线程安全与锁优化

对于高效并发来讲,首先需要保证并发的正确性,然后在此基础上实现高效

线程安全

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的

Java语言中的线程安全

按照安全程度的强弱排序:

不可变:final(String、枚举、Number部分子类)

绝对线程安全:Vector的get和remove,若果刚好remove时去get会ArrayIndexOutOfBoundsException(对Vector进行synchronized)

相对线程安全:Java语言中大多数都是这样Vector、HashTable等

线程兼容:Java API中大部分类都是线程兼容的,如:ArrayList、HashMap

线程对立:很少有,且通常有害,例如:Thread类的suspend()和resume()方法

线程安全的实现方法

互斥同步(阻塞同步):

保证共享数据在同一个时刻只被一条(或者是一些,使用信号量的时候)线程使用

互斥是因,同步是果

synchronized:monitorenter、monitorexit;对于同一个线程是可重入的,同步块在已进入的线程执行完之前会阻塞后面其他线程的进入

JUC包中的重入锁ReentrantLock:等待可中断、可实现公平锁、锁可以绑定多个条件(1.6优化synchronized后就和ReentrantLock差不多了,推荐原生synchronized)

非阻塞同步:

基于冲突检测的乐观并发策略(需要硬件指令集的发展,因为需要操作和冲突检测这两步原子进行)

指令有:测试并设置、获取并增加、交换、比较并交换、加载链接/条件存储

ABA问题:JUC提供一个带有标记的原子引用类“AtomicStampedReference”,可以控制版本保证CAS的正确性(一般这种情况下就使用synchronized)

无同步方案:

可重入代码:都是线程安全的,特征:不依赖存储在堆上的数据和公共的系统资源、用到的状态量都由参数传入、不调用非可重入方法等

线程本地存储:ThreadLocal类实现,每个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值

锁优化

自旋锁与自适应自旋:

线程的切换是耗费资源的,可以在2核以上的线程中共享一份数据资源的,另外一个线程先自旋一会,这个共享资源被释放后就可以执行了,过程中不释放CPU

自旋默认10次,-XX:PreBlockSpin来更改

1.6引入了自适应锁,意味着自旋的时间不再固定,是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定

锁消除

JVM在加载时就判断出不需要枷锁,主要判定依据来自逃逸分析的数据支持

例如:string+

锁粗化

如果虚拟机探测到一串零碎的操作都对同一个对象,将会把加锁扩展到整个操作序列的外部

例如:string+

轻量级锁

本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能损耗

对象头内存布局:

一部分存储对象自身的运行时数据,如哈希吗、GC年龄分代,官方称为Mark Word

另外一部分用于存储指向方法区对象类型数据的指针,如果数组对象,还额外的部分存储长度

轻量级锁提升性能的依据:对于绝大部分的锁,在整个同步周期内都是不存在竞争的

偏向锁

意思是这个锁偏向于第一个获得他的线程,如果锁一直没有被其他线程获取,则持有偏向锁的线程将永远不需要进行同步

1.6中-XX:+UseBiasedLocking开启Java与C/C++编译器比对