线程安全
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
JAVA线程安全
- 不可变
通过java的final关键字来事项。对于基本数据类型,直接用final修饰即可保证不可变性;但是对于对象,需要保证对象的行为不会对其状态产生任何影响。
- 绝对线程安全
- 相对线程安全
对对象单独操作是线程安全的,我们在调用的时候不需要做额外的保障措施。java中经常说的线程安全就是这类。
- 线程兼容
对象本身不是线程安全的,但是可以通过调用端正确的使用同步手段来保证对象在并发环境中安全的使用。
- 线程对立
线程对立是指不管调用端是否采用了同步措施,都无法在多线程环境中并发使用的代码。
线程安全的实现方法
- 互斥同步
保证共享数据只可以被一个线程使用。主要实现手段是synchronized
对象可以多次加锁,每次加锁都会对锁计数加1,释放时减1,为0时锁就释放了。
synchronized同步块对于同一条线程是可以重入的,且同步块在进入的线程执行完成前,会阻塞后面其他线程的进入。
存在问题:
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题。也被称为阻塞同步,是一种悲观的并发策略
2.非阻塞同步
还有一种基于冲突检测的乐观并发策略。先进行操作,如果没有其他线程争用共享数据,那么操作就成功;如果共享数据有竞争,那么就进行自旋等待或是别的补救操作。这种乐观的并发策略不要求我们把线程挂起,被称作非阻塞同步。
新的concurrent中实现这种非阻塞的同步操作,见其中的CAS操作
3.无同步方案
最安全的就是无共享数据需要操作的代码或是线程本地存储的数据。ThreadLocal技术
锁优化
HotSpot在1.6实现很多锁优化技术。
- 自旋锁和自适应自旋
为了让线程等待,我们让线程进入一个忙循环(自旋),这项技术就是所谓的自旋锁。
1.6引入了自适应自旋,就是自旋的实践不再固定,而是根据同一个锁上一次自旋时间及锁拥有者的状态来决定。
2.锁消除
虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测不可能存在共享数据竞争的锁进行消除
3.锁粗化
我们一般会要求同步块的范围尽可能的小,但是如果在一段代码中对同一个对象频繁加锁,堆性能会造成很大的影响。这里我们会通过锁粗化的技术将同步块的范围扩大。这就是锁粗化。
4.轻量级锁
传统的锁由于使用操作系统互斥量来实现,被称为重量级锁。1.6中新增了轻量级锁,在没有多线程竞争的情况下,减少传统锁使用操作系统互斥量产生的性能消耗。
轻量级锁并不会真正的加锁,更多的还是标识一下。
对象中会有一个Mark Word,用于存储对象自身的运行时数据。如果一个线程要获取这个对象的锁,那么线程会在堆栈中创建一个Lock Record,用于存储该对象的Mark Word,同时对象的Mark Word中的锁定参数也会设置为锁定。接下来虚拟机会通过CAS操作来将对象的Mark Word更新为指向线程的Lock Record。
如果CAS操作时,发现Mark Word已经有了指向,那么如果指向的当前线程的栈帧,说明线程已获取该锁,那么直接进入。如果指向不同的线程,说明存在多个线程竞争的情况,那么轻量级锁就不再有效,会直接膨胀为重量级锁。
5.偏向锁
同轻量级锁不一样,偏向锁会保证第一个获得它的线程,如果接下来的执行过程中,该锁没有被其他线程获得,则持有偏向锁的线程将永远不需要再进行同步。
每当有另外一个线程获得这个锁时,偏向锁模式就 宣告结束。