如果一个对象可以安全地被多个线程同时使用,那它就是线程安全的
线程安全级别
Java 中操作共享数据分为以下5类:
- 不可变:不可变的对象一定是线程安全的。只要一个不可变对象被正确的构建出来(没有发生this引用逃逸的情况),其在多线程中不会出现不一致的状态。
- 基本数据类型:使用final修饰即可保证它是不可变的
- 对象:保证对象的行为不会对其状态产生任何影响(可通过把对象中带有状态的变量都声明为final实现)
- 绝对线程安全:不管运行时环境如何,调用者都不需要额外的同步措施
- 相对线程安全:保证对这个对象单独的操作是线程安全的
- 线程兼容:可通过在调用端正确地使用同步手段来保证对象在并发环境下可以安全地使用
- 线程对立:无论调用端是否采用同步措施,都无法在多线程环境下并发使用代码
线程安全的实现方法
互斥同步(阻塞同步)
是一种并发正确性保证手段。 指在多线程并发访问共享数据时,保证共享数据在同一时刻只被一个线程使用。
互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式
synchroized (非公平锁)是最基本的互斥同步手段:
- synchroized 对同一个线程来说是可重入的
- 同步块在已进入的线程执行完之前,会阻塞后面其它线程的进入。
JUC 中的重入锁(ReentrantLock,默认非公平锁)实现同步:
- 等待可中断: 当持有锁的线程长期不释放锁时候,正在等待的线程可以选择放弃等待,改为处理其他的事。
- 可实现公平锁: 公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。
- 锁可以绑定多个条件: 指一个reentrantLock对象可以同时绑定多个condition对象
非阻塞同步
基于冲突检测的乐观并发策略:先进行操作,如果没有其它线程争用共享数据,那操作就成功了。如果共享数据有争用,产生了冲突,那就再采取其他的补偿措施(常见的补偿措施是不断地重试,直到成功为止)
基于冲突检测的乐观并发策略需要操作和冲突检测这两个步骤具备原子性,硬件保证一个从语义上看起来需要多次操作的行为只通过一条处理器指令就能完成
CAS指令需要三个操作数:
- 内存位置(变量的内存地址 V)
- 旧的预期值(A)
- 新值(B) JDK 1.5 之后,当且仅当 V 符合旧预期值A时,处理器用新值 B 更新V的值,否则它就不会更新,但是无论是否更新了V的值,都会返回V的旧值。
CAS操作存在“ABA”问题,大部分情况下ABA问题不会影响程序并发的正确性,如果需要解决ABA问题,可改用传统的互斥同步。
锁优化
为在线程之间更高效的共享数据,以及解决竞争问题
适应性自旋
为了让线程等待,我们只需让线程执行一个忙循环(自旋)
锁消除
锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。
锁粗化
如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。
轻量级锁与偏向锁
轻量级锁用于在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。