研究多线程背景:计算机的运算速度与其存储和通信子系统速度差距太大
目的:充分利用计算机运算能力; 一个服务端同时对多个客户端提供服务。
针对以上问题,分别从硬件和java虚拟机做效率提升。
硬件---------->内存模型(在处理器与存储器间加缓存-->解决缓存一致性)-->代码乱序执行优化
java虚拟机-->java内存模型(工作内存类比缓存)---------------------------------->指令重排序优化
Java内存模型
目的:屏蔽各种硬件和操作系统的差异,达到各平台下一致的内存访问效果。
目标:定义变量(非线程私有的变量)的访问规则。主内存<-->工作内存
主内存与工作内存:<1>所有变量存储在主内存;<2>工作内存保存了被该线程使用到的变量副本(包括volatile)
<3>线程对变量的操作,都在工作内存,不能直接读写主内存;<4>线程间变量的传递都要通过主内存。
定义的操作: 执行引擎 --assign--> 变量副本 工作内存 --store--> 主内存( --write--> 变量)
执行引擎 <--use-- 变量副本<--load-- 工作内存 <--read-- 变量 主内存
操作使用规则:<1>read和load、write和store必须成对出现<2>assign之后一定要写回内存,没有assign一定不能写回内存
<3>use之前必须已经有load操作
<4>一个变量的lock是线程独占的,但这个线程可以对它多次lock,需要执行相同次数的unlock
(unlock前变量同步回主内存),lock操作会清空工作内存中的变量副本;
Volatile:保证<1>新值立即同步到主内存;<2>使用前立即从主内存刷新;
Volatile和一般变量的区别:一般变量不会立即同步到主内存也不会立即刷新
volatile:一条线程A修改只,其它线程立即得知(每次使用之前先刷新,看不到不一致)
一般变量:一条线程修改,另一条线程需要主动read才能得到新值
Volatile场景:
1、运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值
2、变量不需要与其他的变量共同参与不变约束
原子性:lock、unlock、read、load、use、assign、store、write
synchronized具备原子性(更大范围的原子性):它用monitorenter和monitorexit把同步块包起来,而这两个字节码指令真正用的是lock和unlock原子操作。
可见性:volatile、synchronized(unlock前必须同步回主存)、final(构造器中初始化即可见)
有序性:本线程内有序;线程间无序(指定重排、工作内存与主存同步延迟)
volatile(禁止指定重排)、synchronized(一个变量同一时刻允许一个线程lock)、先行发生原则
先行发生:
不满足先行发生原则的都可能被重排
<1>.程序次序规则:按代码的书写顺序发生;<2>.管理锁定:unlock先行发生于后面对同一个锁的lock
<3>volatile变量的写操作先行发生于后面的读;<4>Thread对象的start先行发生于此线程其他动作;
<5>线程的所有操作先行发生于它的线程终止检测;<6>线程中断的调用先行发生于被中断线程的代码检测到中断事件的发生
<7>一个对象的初始化完成先行发生于它的finalize()方法的开始;<8>传递性,A先于B,B先于C,得出A先于C
Java线程
线程的实现
Thread类的所有关键方法都声明为Native
<1>使用内核线程实现,由内核完成线程切换。程序一般不会直接使用内核线程,而是使用轻量级进程(相当于内核线程的高级接口,每个轻量级进程都由一个内核线程支持,一对一的线程模型)。轻量级进程局限:基于内核线程实现,各种操作都需要进行系统调用(用户态和内核态来回切换),代价相对较高; 消耗内核资源。
<2>使用用户线程实现,广义用户线程--只要不是内核线程的(如轻量级进程);狭义用户线程--完全建立在用户空间的线程库上,内核不感知。优点--速度快低消耗,支持规模更大。缺点--资源在内核进程上,对资源的操作异常困难。
<3>用户线程+轻量级进程,保持了用户线程廉价操作(创建、切换、析构等)且支持大规模线程数,轻量级进程作为用户线程与内核线程的桥梁,解决了线程调度功能及处理器映射,用户线程系统调用通过轻量级进程降低进程阻塞风险。多对多模型
java线程实现---根据平台有差异:sun JDK是一对一,Solaris是可配一对一 或 多对多
线程调度:协同式线程调度--一个线程执行完了,它在通知其它线程执行,实现简单,一个线程阻塞整个系统崩溃。
抢占式线程调度--线程调度和分配执行时间完全由系统说了算(也是java的线程调度方式),不会有上面的问题。
抢占式可以通过设置线程优先级来“建议”系统执行某些线程。
(线程优先级不太靠谱:Java线程优先级与不同操作系统提供的优先级不能一一对应)
线程安全与锁优化
面向过程编程思想:
面向对象编程思想:
线程安全定义:
前提:多个线程间存在数据共享。(没有数据共享必然不存在线程安全问题)
5类安全程度:(由强至弱)
不可变-- final,从被构建出来,外部可见状态永远不会变(没有发生this引用逃逸的情况)
绝对线程安全-- 不管运行时环境如何,都不需要任何额外同步措施。
相对线程安全-- 保证这个对象单独操作是线程安全的。java中大多数线程安全类都属于这种,Vector、HashTable、Collections的synchronizedCollection()方法包装的集合等。
线程兼容-- 通过使用同步手段可以实现对象的线程安全。Java API中大部分类都属于这种。
线程对立-- 采取了同步措施也不能保证线程安全。Thread类的suspend()和resume()方法。
线程安全的实现方法:<1>代码编写; <2>虚拟机提供的同步和锁机制;
互斥同步:(悲观并发策略)
同步是指多线程并发访问共享数据,保证同一时刻只被一个线程使用(信号量可有多个)。
通过临界区、互斥量、信号量来实现互斥,来达到同步的效果。
synchronized---- java中最基本的互斥同步手段(同步块对同一线程可重入,线程独占)
synchronized: monitorenter(reference)
同步块
monitorexit(reference)
reference若没有显示指定,则根据它修饰的实例方法还是类方法,取对应的对象实例或Class对象来作为锁对象
synchronized是重量级操作:synchronized被一个线程占有时,阻塞其它线程,可用时再唤醒,
状态转换涉及用户态和内核态(java线程都是映射到操作系统原生线程上),耗费很多处理器时间。
ReentrantLock----另一个同步手段
新增功能:等待可中断、可实现公平锁(设置参数即可)、锁可以绑定多个条件。
非阻塞同步:(基于冲突检测的乐观并发策略)
硬件支持:(操作和冲突检测一起具备原子性)
测试并设置(Test-and-Set)、获取并增加(Fetch-and-Increment)、交换(Swap)、比较并交换(Compare-and-Swap CAS)、加载链接/条件存储(LL/SC)
CAS的ABA问题
无同步方案:可重入代码、线程本地存储(ThreadLocal数据块在同一线程共享)
锁优化
自旋锁---针对场景共享数据锁定状态持续很短时间,挂起和恢复线程耗时占比太大不划算(优化一些synchronized),稍等一下不放弃处理器执行时间,线程忙循环(自旋);忙循环次数是可控的,自适应自旋锁---根据前一次在同一个锁上的自旋时间及锁的拥有者的状态,由虚拟机来控制次数。
锁消除---对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。
锁粗化----一段代码不停加解锁效率低,把锁范围扩大。
轻量级锁---在没有多线程竞争前提下,减少重量级锁的使用操作系统互斥量带来的性能消耗。
偏向锁----消除无竞争下的同步,不做任何同步