并发处理的广泛应用使得Amdahl定律代替摩尔定律成为计算机性能发展源动力的根本原因。
一、线程安全
当多个线程访问一个对象时,如果不用考虑这些线程在运行环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以得到正确的结果,那这个对象时线程安全的。
·
1.1Java语言中的线程安全
- 不可变
final修饰的不可变对象(如果是一个对象,则需要保证对象的行为不会对状态发生改变) String - 绝对线程安全
不管运行时环境如何,调用者都不需要任何额外的同步措施 - 相对线程安全
通常意义上讲的线程安全,需要保证对这个对象单独的操作时线程安全的。Vector - 线程兼容
本身并不是线程安全的,但是可以在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用,Java API中大部分属于线程兼容的 - 线程对立
无论调用端是否采取了同步措施,都无法再多线程环境中并发使用的代码。Thread类的suspend()和resume()方法
1.2 线程安全的实现方法
1.2.1 互斥同步
同步是指在多个线程并发访问共享数据时,保证共享数据在同一时刻只能被一个(或一些)线程使用。而互斥是实现同步的一种手段。Synchronized
java.tuil.concurrent的重入锁 ReentrantLock,有3点高级功能
- 等待可中断,当持有锁的线程长期不释放锁时,正在等待的线程可以放弃等待,改为处理其他事情
- 公平锁,指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁,synchronized是非公平的,ReentrantLock默认下也是非公平的
- 锁绑定多个条件,一个ReentrantLock对象可以同时绑定多个Condition对象
1.2.2 非阻塞同步
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。这是一种悲观策略。
还有一种基于冲突检测的乐观并发策略,先操作,如果没有其他线程争用共享数据,就成功,如果有争用,就产生冲突,再采用其他补偿措施。这种同步操作称为非阻塞同步。
需要硬件指令集的发展:
例如 比较并交换(Compare-and-swap, CAS)
java.util.concurrent.atomic.AtomicInteger
1.2.3 无同步方案
同步只是保证共享数据争用时的正确性的手段,如果一个方法本身不涉及共享数据,那自然无需任何同步。
- 可重入代码
即同一输入,必然同一输出,方法返回结果可预期 - 线程本地存储
Web交互模型
线程隔离-线程Code k-v
二、锁优化
JDK 1.6
2.1 自旋锁与自适应自旋
为了让线程等待而不是阻塞导致大的系统开销,只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁
默认自旋次数,自旋多了会占用CPU开销
JDK1.6引入了自适应的自旋锁,自旋次数不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者来决定
2.2 锁消除
虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。
主要判定依据来源于逃逸分析的数据支持
2.3 锁粗化
如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围拓展到整个操作序列外部
2.4 轻量级锁
加锁:如果同步对象没有被锁定(01),虚拟机首先将在当前栈帧中简历一个名为锁记录的Lock Record)空间,用于存储对象目前Mark Word的拷贝,然后虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,如果更新成功了,那么这个线程就拥有了该对象的锁,标志位变为00,如果有两个线程争用同一个锁,那么轻量级锁不再有效,膨胀为重量锁。
解锁:CAS操作把对象当前的Mark Word和栈帧中的Lock Record替换回来,如果替换成功,则同步过程完成了。
在有竞争的情况下,还额外发生了CAS操作,轻量级锁会比传统的重量级锁更慢
2.5 偏向锁
当锁对象第一次被线程获取,标志位设置为 01,偏向模式,CAS操作把线程id记录在Mark Word中,如果操作成功,则该线程无需进行任何同步操作。如果有另外一个线程尝试获取这个锁,偏向模式宣告结束,恢复到轻量级锁等状态。
偏向锁可以提高带有同步但无竞争的程序性能,它同样是一个带有效益权衡性质的优化,不一定总是有利,如果程序中大多数锁总是被多个不同线程访问,那么偏向模式就是多余的。可以用参数禁止。