第13章线程安全与锁优化

线程安全

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

Java语言中各种操作共享的数据分为以下五类:

  1. 不可变:用final来修饰一个基本数据类型或是对象所在的类
  2. 绝对线程安全:在Java API中标注自己是线程安全的类,大多数都不是绝对的线程安全
  3. 相对线程安全:即通常所说的线程安全,代表类:Vector,HashTable,Collections的synchronizedCollection()方法
  4. 线程兼容:即通常所说的线程不安全,代表类:ArrayList和HashMap
  5. 线程对立:指调用段是否采取了同步措施,都无法在多线程环境中并发使用的代码,代表:Thread类的suspend()和resume()方法

线程安全的实现方法:

  1. 互斥同步(阻塞同步):对线程进行频繁的阻塞和唤醒会带来性能问题,属于一种悲观的并发策略。
  2. 非阻塞同步(通过处理器指令集实现,代表:CAS),是一种乐观并发策略,先进行操作,如果没有其他线程争用共享数据,那操作就成功了,如果共享数据由争用,产生了冲突,那就再采取其他的补偿措施(如不断地重试,直到成功为止),这种方法不需要把线程挂起。
  3. 无同步方案:如果一个方法本身就不涉及共享数据,自然无需同步措施去保证正确性,天生是线程安全的代码分为两类:可重入代码线程本地存储

什么是可重入代码:相对于线程安全的特性来说,可重入性是更基本的特性,它可以保证线程安全;但是反过来,线程安全并不保证可重入性

线程本地存储:把共享数据的可见范围限制在同一个线程之内,如ThreadLocal类实现线程本地存储功能

ReentrantLock与synchronized都具有互斥的特点用来实现同步,不过reentrantLock具有以下三个高级功能

  1. 等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他的事情,对执行时间非常长的同步块很有用
  2. 可实现公平锁:多个线程等待同一个锁时,必须按照申请锁的时间顺序来一次获得锁,而非公平锁则不保证这一点,在锁被释放的时候,任何一个等待锁的线程都有机会获得锁
  3. 锁可以绑定多个条件:一个ReentrantLock对象可以同时绑定多个Condition对象

ReentrantLock的弊端:吞吐量不如synchronized

CAS指令需要3个操作数,分别是内存位置(V),旧的预期值(A),新值(B)。CAS 指令执行的时候,当且仅当V符合旧预期值A时,处理器用新值B更新V值,否则它就不执行更新,但是无论是否更新了V的值,都会返回V的旧值。这个操作是一个原子操作

第三节 锁优化

自旋锁:JDK1.6默认开启自旋锁,因为挂起和恢复线程的操作都需要转入内核态来完成,这样会给系统的性能带来很大的压力,特别是在共享数据的锁定状态只维持很短的时间的情况下,因此引出了自旋锁是在有两个以上的处理器的情况下,让多个线程同时工作,如果后面的线程请求锁,就让它稍等一下,但不放弃处理器的执行时间,这时会让该线程执行一个忙循环。

锁粗化:一般情况下推荐奖代码块的作用范围限制的尽量小,这是做细化,但是如果一系列的连续操作都对同一对象进行反复加锁,解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。通常情况下,将加锁同步的范围扩大到整个操作序列的外部。

轻量级锁:JDK1.6新增的锁机制,在没有多线程竞争的前提下,减少传统重量级锁使用操作系统互斥量产生的性能损耗。

猜你喜欢

转载自blog.csdn.net/wo8vqj68/article/details/85999944