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++编译器比对