线程安全与锁优化
线程安全
-
较为恰当的定义:“当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他协调操作,调用这个对象的行为都可以获得正确的结果,那就成这个对象是线程安全的”
-
Java语言中的线程安全:将Java语言中各种操作共享的数据分为以下五类:不可变、绝对线程安全、相对线程安全、线程兼容、线程对立
-
不可变:
/* Java语言里,不可变的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要再进行线程安全保障措施。 Java语言中,若线程共享的数据是一个基本数据类型,那么只要在定义时使用final关键字修饰它就可以保证它是不变的。若共享数据是一个对象,由于Java语言目前暂时还没有提供值类型的支持,那就需要对象自行保证其行为不会对其状态产生任何影响才行。 */
-
绝对线程安全
-
相对线程安全:通常意义上所讲的线程安全,需要保证对这个对象单次的操作是线程安全的,在调用的时候不需要进行额外的保障措施;Java语言中,大部分声称线程安全的类都属于,如Vector、Hashtable、Collections的synchronizedCollection()方法包装的集合等;
-
线程兼容:指对象本身并不是线程安全的,但是可通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用;
-
线程对立:指不管调用端是否采取了同步措施,都无法在多线程环境中并发使用代码。
-
线程安全的实现方法
-
互斥同步
/* 互斥同步是一种最常见也是最主要的并发正确性保证手段。 同步:指多个线程并发访问共享数据时,保证共享数据在同一时刻值被一条(或一些,当使用Semaphore信号量时)线程使用。 互斥:实现同步的手段,临界区、互斥量、信号量都是常见的互斥实现方式。 Java语言中,最基本的互斥手段为synchronized关键字; 主要问题:进行线程阻塞与唤醒锁带来的开销,因此该同步也被称为阻塞同步。 */
-
非阻塞同步
/* 互斥同步(阻塞同步)体现的是一种悲观锁思想:即无论共享数据是否真的会被多个线程竞争,它总是认为只要不去做正确的同步,那就肯定会出问题。 非阻塞同步:基于冲突检测的乐观并发策略(即不管是否存在风险,先进行操作,若无其他线程竞争共享数据,那么操作成功;反之,存在竞争产生冲突,那再进行其他补偿措施,最常用的补偿措施就是不断重试,直到成功) 基于乐观并发策略的实现,不需要再将线程阻塞挂起。 常用处理器指令(以下操作均具备原子性): 测试并设置(Test-and-Set) 获取并添加(Fetch-and-Increment) 交换(Swap) 比较并交换(Compare-and-Swap) 加载链接/条件存储(Load-Linked/Stored-Condition) */
-
无同步方案
/* 要保证线程安全,也并非一定要进行阻塞或非阻塞同步,同步与线程安全两者没有必然的练习。 同步只是保障在共享数据争用时正确的手段,如果一个方法本来就不实际共享数据,那自然不需要任何同步措施保证其安全性。 */
锁优化
-
自旋锁与自适应锁
-
锁自旋:为了让一个线程等待获取锁但不进入阻塞状态,便只需让该线程自旋(循环执行空操作)
-
自旋锁可通过-XX:+UseSpinning参数来开启自旋锁,JDK1.6中已默认开启,自旋次数的默认值为10,用户也可通过-XX:PreBlokcSpin来更改;
-
-
锁消除
-
指虚拟机即时编译器在运行时,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除。锁消除主要判定依据来自逃逸分析数据的支持。
-
-
锁粗化
-
若虚拟机探测到一串零碎的操作都是对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部
-
-
轻量级锁
-
JDK1.6中引入的新型锁机制,HotSpot虚拟机的对象头分为两部分信息Mark Word以及Klass Word;前者Mark Word中存储的是该对象的HashCode、GC分代年龄等信息,该部分数据在32bit和64bit的计算机中分别占32位和64位。
-
另外一部分用于存储指向方法区对象类型数据的指针;若为数组对象的话,还会存在一个额外的部分用于存储数组长度。
-
/* 当代码进入同步块时,若同步对象未被锁定(即末两位标志位为“01”状态),虚拟机首先在当前栈帧中建立锁记录(Lock Record)的空间,用于存储对象目前的Mark Word的拷贝信息; 然后虚拟机将使用CAS方式尝试将对象的Mark Word更新为指向Lock Record的指针。若更新动作成功,表名该线程拥有了该对象的锁,同时间将对象头Mark Word改为指向该锁记录的引用,同时将末两位标志位置为“00”状态,即表示此对象处于轻量级锁定状态。 若更新失败,虚拟机首先检查对象的Mark Word是否指向当前线程的栈帧,若是,即表名当前线程已经拥有该对象的锁,可以直接进入同步块内继续执行;否则说明当前对象锁已被其他线程抢占。此时出现两个线程同时竞争一个锁,则进入锁膨胀阶段就,轻量级锁不再有效,转而膨胀微重量级锁“10”状态,且Mark Word中存储的就是指向重量级锁(互斥锁)的指针,后面再尝试获得锁的线程都要进入阻塞状态。 */
-
-
偏向锁
-
锁偏向是JDK1.6中引入的一项锁优化,目的是消除无竞争情况下的同步原语,进一步提高程序的运行性能。若轻量级锁时在无竞争的情况下通过CAS的方式去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下将整个同步都清除掉,即连CAS操作都不进行。
-