线程安全与锁优化

1 线程安全

1)Java语言中的线程安全

1 不可变
Java语言中,如果共享数据是一个基本数据类型,只要在定义时使用final关键字修饰它就可以保证它是不可变的。
如果共享数据是一个对象,那就需要保证对象的行为不会对其状态产生任何影响才行,其中最简单的就是把对象中带有状态的变量都声明为final,这样在构造函数结束之后,它就是不可变的。
2 绝对线程安全
vector虽然是线程安全的容器,但是方法之间不安全。
3 相对线程安全
它需要保证对这个对象单独的操作是线程安全的,但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。
4 线程兼容
对象并不是线程安全的,但是可以在调用端正确的使用同步手段来保证对象在并发环境中安全地使用。
5 线程对立
无法。

2)线程安全的实现方法

1 互斥同步

同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条(使用信号量的时候,是一些)线程使用。
互斥是实现同步的一种手段,临界区、互斥量、信号量都是主要的互斥实现方式。

synchronized:可重入;重量(虚拟机会优化,如:自旋);

ReentrantLock:多增加了一些高级功能,如:等待可中断,公平锁,锁绑定多个条件。

两种性能几乎持平。

2) 非阻塞同步

互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因此也被称为阻塞同步。另外,互斥同步也属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。

非阻塞同步:基于冲突检测的乐观并发策略,通俗的说就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再进行其他的补偿措施(最常见的补偿措施是不断地重试,直到试成功为止),这种乐观的并发策略的许多实现都不需要把线程挂起。

CAS:
虚拟机系列之 线程安全与锁优化
ABA问题:
J.U.C包为了解决这一问题,提供了一个带有标记的原子引用类“AtomitStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。

3)无同步方案
可重入代码和线程本地存储

2 锁优化

这些技术都是为了在线程之间更高效地共享数据,以及解决竞争问题,从而提高程序的执行效率。

1)自旋锁与自适应自旋

挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性能带来了很大压力。同时,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面那个线程“稍等一会儿”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。

但是自旋等待不能代替阻塞,且先不说对处理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,若锁被占用时间很长,则白白消耗处理器资源。所以自旋等待的时间要有限度。

自适应意味着自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
虚拟机系列之 线程安全与锁优化
虚拟机系列之 线程安全与锁优化

2)锁消除

锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。

3)锁粗化

虚拟机系列之 线程安全与锁优化

4)轻量级锁

轻量级锁的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

虚拟机系列之 线程安全与锁优化
虚拟机系列之 线程安全与锁优化
虚拟机系列之 线程安全与锁优化

5)偏向锁

偏向锁的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了。
虚拟机系列之 线程安全与锁优化
虚拟机系列之 线程安全与锁优化

相关文章: