Java线程
volatile
- 当变量声明为volatile时,变量将具备以下两个特性:
- 保证此变量对所有线程的可见性,即一个线程修改了volatile变量后,其余线程可以立即获得修改后的值。
- 禁止指令重排序优化,即设置内存屏障,保证volatile变量修改更新到所有CPU上
Java线程实现
- 基于操作系统的原生线程模型。Windows和Linux下都使用一对一的线程模型。
线程状态及转换
- 新建:创建后未启动的线程
- 运行:正在执行或者等待CPU为其分配运行时间
- 无限期等待:等待被其它线程显式地唤醒,不会被分配CPU时间,例如没有设置Timeout的
Object.wait()
或者Thread.join()
- 限期等待:不会被分配CPU时间,但是不需要其它线程显式地唤醒,一定时间后会由系统自动唤醒,例如
Thread.sleep()
,设置了Timeout的Object.wait()
或者Thread.join()
- 阻塞:等待获取一个排他锁。
- 结束:已终止的线程状态。
线程安全
线程安全程度
- 不可变:无论是对象的方法实现还是方法的调用者,都不需要再采取任何的线程安全保障措施。例如final关键字修饰的变量
- 绝对线程安全:调用者不需要任何额外的同步措施。
- 相对线程安全:保证对对象的单独操作是安全的,调用者不需要做额外的保障措施。但对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性(可以保证调用对象不同的方法时,不同的方法不会交叉执行,但是无法保证一组方法的调用顺序)。例如对
Vector
对象同时进行remove()
和get()
操作时可能会出现删除第i个元素后面是访问第i个元素的情况。在Java语言中,大部分的线程安全类都属于这种类型。
- 线程兼容:对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全使用。
- 线程对立:无论调用端是否采取同步措施,都无法在多线程环境中并发使用的代码。
线程安全的实现方法
互斥同步
- 保证共享数据在同一时刻只被一个(使用信号量的条件下是一些)线程使用
- 是一种悲观的同步策略
- 在Java中使用
synchronized
关键字进行同步。
非阻塞同步
- 基于冲突检测的乐观并发策略,即先进行操作,如果没有其它线程竞争,那么操作成功;反之,再采取其它补偿措施(最常见的措施是不断重试,直到成功为止),需要硬件保证操作和冲突检测两个步骤具备原子性。
- CAS指令:当且仅当内存值和预期值相等时,使用新值更新内存值,否则不更新。
- ABA问题:在某线程获取变量值时是A,在检查之前被改为了B,然后又恢复了A,检查时会认为该变量没有修改过。多数情况下该问题不影响程序的并发,如需解决,改为使用互斥同步可能更有效。
无同步方案
- 可重入代码:可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),在中断返回后,原来的程序不会出现任何错误。
- 线程本地存储:把使用共享数据的代码放入同一线程中。
锁优化
自旋锁
- 让请求锁的线程进行短暂的等待,看锁是否很快就被释放。
- 避免了线程切换开销,但是等待时间过长会浪费CPU资源
- 自适应自旋锁:是否自旋、自旋时间不再确定,由前一次在同一个对象上的自旋锁时间和拥有者的状态决定。
锁消除
- 对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。判定依据来源于逃逸分析。
- 一些线程同步代码是内嵌的Java内部的,可以通过锁消除进行优化。
锁粗化
- 如果有连续的对同一个对象加锁,则虚拟机会把加锁同步的范围扩大至整个操作序列外部,以减少加锁解锁带来的性能损耗。
轻量级锁
- 使用CAS操作和对象的Mark Word实现的锁机制。
- 前提是绝大部分的锁在同步周期内是不存在竞争的,因此CAS操作避免了使用互斥量的开销。但是如果存在锁竞争,轻量级锁比传统的互斥同步还多出CAS操作的消耗。
偏向锁
- 该种锁偏向于第一个获得它的线程,在接下来的执行过程中,如果该锁没有被其它线程获取,则持有偏向锁的线程将永远不会进行同步。一旦有其它线程获取该锁,偏向模式即结束,转变为传统的互斥同步。
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.