Fari

线程锁

线程安全与锁优化

多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调用和交替执行,也不需要额外的同步,或者在调用方进行任何协调操作,都能获得正确的结果,那么这个对象是线程安全的

可以将java语言中的各种操作共享数据分为5类

  1. 不可变:Immutable对象、String或被finnal修饰的基本数据类型。对引用类型,只要其地址不变,且其不会暴露出影响其状态的方法,也是不变的。例如String,其本身内部使用final修饰,且其所有的方法都不会改变该值,而是新建一个String对象,所以String是不可变的
  2. 绝对线程安全:条件相当严苛,即在任何情况下都线程安全
  3. 相对线程安全:对象单独的操作是线程安全的。例如 Vector(所有的方法都被同步修饰)、HashTable
  4. 线程兼容:对象本身不是线程安全的,但是通过使用同步手段可以达到该目的,例如使用锁或者同步机制。如ArrayList和HashMap
  5. 线程对立:无论是否使用同步手段,都无法保证线程安全。Thread类的suspend()和resume()方法,如果一个线程执行了suspend()但逻辑上又需要执行resume()则就发生了死锁

线程安全的实现方法

互斥同步(阻塞同步)

同步是指在多线程并发访问共享数据时,保证同一时刻只能被一个线程使用

非阻塞同步

互斥同步最主要的问题是进行线程阻塞和唤醒所带来的性能问题。互斥同步署于一种悲观的并发策略。而非阻塞同步则是:先进行操作,如果没有其他线程争用共享数据,那操作就是成功了,如果有冲突,则再采取其他补偿措施(最藏剑的就是不断重试,直到成功,即CAS)。因为其不需要将线程挂起,所以是非阻塞的。

无同步方案

要保证线程安全,并不一定要使用同步。同步只是保证共享数据争用时的正确性手段。如果一个方法本来就不涉及共享数据,那他天然就是线程安全的。

锁优化

自旋锁与自适应自旋

互斥同步对性能影响最大的时阻塞的实现(线程的阻塞和唤醒需要用户态和内核态的切换),且由于共享数据的锁定状态只会维持很短一段时间,所以可以让等待的线程不放弃处理器执行时间而执行一个忙循环(自旋) 因为不会放弃cpu执行时间,所以对于长时间的等待自然是一种浪费,如果自旋超过了一定次数仍没有获得锁,就是使用传统的方式挂起线程了。自旋次数默认为10次,可以通过 -XX:PreBlockSpin 参数修改 JDK1.6引入了自适应自旋锁,如果之前能很快自旋成功,那么下次自旋将会允许更长的时间,反之则会更快升级为挂起

锁消除

指虚拟机即使编译器在运行时,对一些要求同步的代码进行分析,检测到其不可能存在共享数据竞争的关系,则会对锁进行消除。 锁消除的主要判定依据来源于 逃逸分析 的数据支持。

锁粗化

如果虚拟机探测到有一串零碎的操作都对同一个对象加锁,则虚拟机会将锁扩展到整个操作序列以外,内部的锁就会被消除

轻量级锁

轻量级锁不能代替重量级锁,其本意时在没有多线程的竞争前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗 HotSpot虚拟机的对象头分为两个部分。第一部分(Mark Word)用于存储对象自身的运行数据,如hashCode、GC分代年龄等,它是实现轻量级锁和偏向锁的关键。另一部分用于存储指向方法区对象类型的指针数据,如果是数组对象的化,还会有一个额外的部分用于存储数组长度 在Mark Word中,使用2bit记录锁状态 file

轻量级锁的缺点:如果存在竞争,则处理互斥量(加重量级锁)的开销外,还额外发生了CAS

偏向锁

如果说轻量级锁是在无竞争情况下使用CAS去消除同步使用的互斥量,那偏向锁就是在无竞争情况下把整个同步都消除掉,连CAS都不做了 偏向锁会偏向第一个获取它的线程,如果接下来执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程永远不需要同步