线程安全的实现方法
非阻塞同步
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因此这种同步也成为阻塞同步。
随着硬件指令集的发展,我们有了另外一个选择:
基于冲突检测的乐观并发策略,通俗的说,就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再采取其他的补偿措施(最常见的补偿措施就是不断地重试,直到成功为止),这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步操作称为非阻塞同步。
硬件保证一个语义上看起来需要多次操作的行为只通过一条处理器指令就能完成,这类指令常用的有:
- 测试并设置
- 获取并增加
- 交换(Swap)。
- 比较并交换(Compare-and-Swap,下文称CAS)。
- 加载链接/条件存储
在IA64、x86指令集中有cmpxchg指令完成CAS功能。
CAS指令需要3个操作数,分别是内存位置(用V表示)、旧的预期值(用A表示)和新值(用B表示)。CAS指令执行时,当且仅当V符合旧预期值A时,处理器用新值B更新V的值,否则它就不执行更新,但是无论是否更新了V的值,都会返回V的旧值,上述过程是一个原子操作。
在JDK1.5之后Java中由 sun.misc.Unsafe 类里面的 compareAndSwapInt() 和 compareAndSwapLong() 等几个方法包装提供。
但是我们只能通过Java API来间接地使用它,如 J.U.C 包里面的整数原子类,其中的 compareAndSet() 和 getAndIncrement()等方法都使用了Unsafe类的CAS操作。
CAS操作的”ABA问题”。
锁优化
自旋和自适应自旋
互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性能带来了很大的压力,而且共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。
为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。
锁消除
锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。
锁粗化
我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小–只在共享数据的实际作用域中才进行同步。
如果虚拟机探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。
轻量级锁
轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能损耗。
偏向锁
偏向锁也是JDK1.6中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。
如果说轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不用做了。