线程安全
在Java中可以对各种操作共享数据分为以下5类:不可变、绝对线程安全、相对线程安全、线程兼容、线程对立。
1)不可变
用final修饰的基本变量、行为不会对自己的状态产生影响的对象(比如,把自己的状态设置为final的)、枚举类、java.lang.Nuncer的部分子类(Long,Double等的数值包装类)、BigInteger和BigDecimal等大数据类型。(AtomicInteger和AtomicLong并非不可变)
2)绝对线程安全
即使,一个对象的自身行为全部是同步的,在多线程环境下也不能保证线程的绝对安全。
3)相对线程安全
Java的所谓得线程安全的集合类大部分都是相对线程安全的,需要在调用端加上额外的同步操作。
4)线程兼容
对象本身并不是线程安全,而是通过在调用端正确使用同步手段保证对象的线程安全。如ArrayList、HashMap等。
5)线程对立
无论调用端采用哪种同步操作,都无法在多线程环境下并发使用的代码。
线程安全的实现方法(虚拟机层次)
1)互斥同步
互斥是实现同步的一种手段,临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphone)都是互斥实现方式。
Syncronized同步块,编译后会在同步块前后,加入monitorenter、monitorexit指令。线程阻塞或唤醒时,需要内核调度,要进行用户态核心态的切换,很耗时。虚拟机会进行优化,如自旋等待。
ReentrantLock,也可实现同步功能(lock、unlock、配合try/finally完成),而且比Syncronized多了高级功能:等待可中断、可实现公平锁、锁可绑定多个条件。如下图所示:
2)非阻塞同步
乐观同步策略
3)无同步方案
- 可重入代码块:例如不依赖存储在堆上的数据和公用的系统资源
- 线程本地存储:ThreadLocal的实现
锁优化
1)自旋锁与自适应锁
自旋锁:为了防止同步造成的阻塞对性能的影响,让获取不到锁的线程先进行一个忙循环(自旋),暂时不放弃CPU,这就是自旋锁。
自适应锁:自旋时间不再固定,由历史情况计算时间(很智能啊~)
2)锁消除
编译器优化掉不需要的锁。
3)锁粗化
如果检测到短时间内对同一个对象反复加锁(比如,在一个循环里面有同步代码块),就会把加锁范围扩大(比如,就扩大到循环外围),这样就只有枷锁一次。
4)轻量级锁
对于绝大部分的锁,在整个同步周期都是不存在周期的,使用轻量级锁的话,在没有竞争时就不会有互斥的开销,若有竞争就会转化为重量级锁(在有竞争的情况下,轻量级锁比重量级锁更慢,有一个判断竞争的过程)。
5)偏向锁
当拥有偏向锁的对象第一次被一个线程获取锁的时候,对象就会置为“偏向模式”,持有偏向锁的线程以后每次进入这个锁得同步块时,虚拟机可以不用任何同步操作。直到,另一个线程尝试获取此锁时,“偏向模式”宣告结束。