线程安全
Java语言中的线程安全
线程安全的“安全程度”由强至弱来排序,将Java语言中的各种操作共享的数据分5类:
- 不可变
- 绝对线程安全
- 相对线程安全
- 线程兼容
- 线程对立
不可变
final保证基本数据类型不可变,如果是对象,则需保证对象的行为不会对其状态产生影响才行,例如String的replace等方法不会影响它原来的值。保证对象行为不影响自己状态的途径有很多种,最简单的是把对象中带有状态的变量声明为final。
绝对线程安全
定义:不管运行时环境如何,调用者都不需任何额外的同步措施,在Java体系中标注自己是线程安全的类,大多数都不是绝对的线程安全。
相对线程安全
相对线程安全就是通常意义上的线程安全,他需要保证对这个对象单独的操作是线程安全的。我们在调用的时候不需要做额外的保障措施,但是对于一些特定顺序的连续调用就需要额外的同步手段来保证调用的正确性。在Java语言中大部分线程安全类都属于这种。如Vector,HashTable,Collections的synchronizedCollection方法包装的集合。
线程兼容
线程兼容指对象本身不是线程安全,但可以通过调用端使用同步手段保证在并发下安全使用。如ArrayList和HashMap等。
线程对立
无论调用端是否采取了同步措施,无法在多线程环境下并发使用。在Java语言下很少出现。
线程安全的实现方法
互斥同步
通过互斥达到同步(共享数据在同一时刻被一个线程使用)
在Java中,最基本的互斥同步手段就是synchronize关键字。Java的线程是映射到操作系统的原生线程之上的,阻塞和唤醒一个线程,需要操作系统帮忙完成,这就需要从用户态转到核心态,状态转换需要耗费很多处理器时间。很可能大于简单代码块的执行时间。虚拟机也会做优化,避免频繁切入核心态中。
除了synchronize外,还有java.util.concurrent包中的重入锁(ReentrantLock)来实现同步,基本用法上相似。
ReentrantLock高级功能:
- 等待可中断:当持有锁的线程长期不释放的时候,正在等待的线程可放弃等待。
- 公平锁:按申请锁的时间依次获得锁,非公平锁:每个等待锁的线程都有机会获得锁,synchronize非公平,ReentrantLock默认非公平,可指定为公平锁
- 锁可以绑定多个条件
synchronize和ReentrantLock的对比:
在1.6以后,synchronize和ReentrantLock性能基本持平,在synchronize能实现需求的情况下优先考虑synchronize。
非阻塞同步
互斥同步主要问题就是进行线程阻塞和唤醒带来的性能问题,同步互斥也称阻塞同步。互斥同步属于一种悲观并发策略。总认为会出问题,需要加锁。随着硬件指令集的发展,另一个选择是:基于冲突检测的乐观并发策略,就是先操作,如果有问题在补救。这种同步操作称为非阻塞同步。
JDK1.5以后,Java程序中可使用CAS操作,由sun.misc.Unsafe类的compareAndSwapInt和compareAndSwapLong几个方法包装提供。
无同步方案
保证线程安全,并不是一定要进行同步。同步只是保证共享数据征用时的正确性的手段。如果不涉及共享数据则不需要同步措施。
可重入代码
线程本地存储
ThreadLocal
锁优化
高效并发是从JDK1.5到JDK1.6的重要改进,HotSpot花费大量时间实现各种锁优化来实现在线程之间高效共享数据:
- 自旋锁与自适应自旋:当机器能并行处理两个线程,后面请求锁的线程需要“稍等一下”,但不放弃处理器执行时间。让他执行一个忙循环(自旋)等一等。但不等太久,只适应会自己判断等好久。
- 锁消除:代码上要求同步,但是被检测到不需要同步的。会对锁进行消除
- 锁粗化:原则上是把同步块作用域变小,但是如果需要对同一对象反复加锁解锁会很消耗性能,这时可以将同步域扩大。减少加锁次数。
- 轻量级锁:一个线程进入同步块后,锁对象就会进入轻量级锁定状态,如果两个以上的线程争用同一锁,轻量级锁则会膨胀为重量级锁。后面等待锁的线程也会进入阻塞状态。轻量级锁能提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期都不是存在竞争的”
- 偏向锁:如果轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了。
网友评论