深入理解Java虚拟机----(十一)线程安全与锁优化

线程安全
    为了深入讨论,不把线程安全当做非真即假的概念,我们把它分为5个层次。
  • 不可变:不可变的数据是线程安全的。例如前面提到的final,被final修饰的基础数据类型,被正确构造后,就是不可变的,亦为线程安全的。如果是一个对象,需要保证他的行为不会对状态产生印象,例如String、Integer、Long、Double等,内部值是final的,所以不可变。
  • 绝对线程安全:一个类要想达到绝对线程安全,需要做到无论什么时候,都不需要额外的同步措施。这需要很大很大的代价。大多Java API标记的线程安全类,都不是绝对线程安全。比如Vector,它的get、add、size等方法都是被synchronized修饰的,单独调用时是安全的,但以一些特殊顺序调用一系列的方法时,就不安全了。
  • 相对安全:对一个对象的一个单独操作是线程安全的。例如Vector、HashTable等。
  • 线程兼容:本身不安全,通过同步手段可以达到安全的目的。Java API中大多数都是这类的。
  • 线程对立:无论是否同步,都不能在多线程中并发使用。suspend和resume等。

实现线程安全
  • 互斥同步:同步是指一个共享资源,同一时刻只能被一个线程访问。互斥是实现同步的方法。
    • Java实现互斥同步的基本办法就是synchronized,它在编译时会在代码块前后生成monitorenter和monitorexit两个指令,这俩个指令以同步对象作为参数。synchronized是可重入的;synchronized会阻塞后面线程对锁的获取。由于前面讲过Java线程模型是1对1的,一个java线程对应一个操作系统原生线程,线程的阻塞和唤醒需要操作系统帮忙,需要从用户态转换到内核态,这个开销很大,所以synchronized是重量级的操作。
    • 还可以同concurrent中的可重入锁ReentrantLock实现。二者都是可重入的,但后者功能更丰富:
      • 等待可中断:等待的线程可以主动放弃等待。
      • 可实现公平锁:获得顺序与阻塞顺序相同。
      • 绑定多个条件:可以绑定多个Condition,synchronized只能wait notify绑定一个隐含的条件。
    • 二者的性能上,虽然synchronized更重一些,但是作为jdk的实现,许多优化措施已经被发布,而且未来的优化空间还有很大,所以synchronized可以实现的话,还是推荐使用synchronized。
  • 非阻塞同步:互斥同步因为会阻塞线程,称为阻塞同步,开销很大。这是一种悲观的并发策略,认为不同步就会出问题。随着硬件发展,出现了非阻塞同步的方法,这是一种乐观的策略。它采用先行模式,就是先进行数据操作,如果没有竞争,那操作就成功了。如果发生冲突,再补救,通常是循环重试。这就避免了线程的挂起。这种方案需要计算和冲突检测两个步骤具有原子性,实际是通过硬件提供的指令:
    • 检测并设置
    • 获取并增加
    • 交换
    • 比较并交换 CAS
    • 加载链接/条件存储 LL/SC
          JDK1.5后, sun.misc.Unsafe类提供了对CAS的包装,这个类不是给用户用的,只有通过启动类加载器加载的类才能使用。只能通过反射或其他封装类库使用,例如Atomic。这种方案存在ABA问题,尽管提供了AtomicAtampedReference通过版本解决,但如果非解决不能,还不如互斥同步的效率好。

锁优化
    JDK1.6对锁进行了大量优化措施,来提高并发效率。
  • 自旋锁:线程的挂起和恢复代价很大,自旋锁让竞争锁的线程自循环等待一会,看不能不能很快获得锁。省去了线程切换代价,但是自循环白白消耗cpu,所以它会自适应,收集数据判断等待是否值得,默认循环10次,如果预测值得就会加大次数,如果预测不值得,可能会直接挂起。
  • 锁消除:利用逃逸分析的结果,去掉不必要的锁
  • 锁粗化:如果小范围内频繁对一个对象加锁、释放锁,开销很大,会将锁范围扩大,用更少量的锁代替,减少开销。
  • 轻量级锁:传统锁使用互斥量实现,开销大。它的目的是 在没有多线程竞争的前提下,减少传统锁的开销。前面的对象内存布局中提到过,HotSpot对象头会存储运行时信息Mark Word,其中就包括锁信息。如果线程发现对象当前没有被锁定,将会在栈桢建立一个名为锁记录的空间,存储当前锁对象的Mark Word拷贝,然后用CAS尝试将对象头中的这部分改为指向该锁记录的指针。如果成功了,就获得了锁,并将Mark Word标记为轻量级锁定状态。如果失败了,看占有锁的是不是这个线程本身,是的话,可以继续执行,不是的话,说明产生竞争了,轻量级锁将会膨胀为重量级锁。释放锁同样CAS。可以看出,如果没有多线程竞争,大大提升性能,但是如果竞争出现,在传统锁基础上又绕远了一圈。
  • 偏向锁:目的是消除无竞争下的锁原语,进一步提升性能。轻量级锁是用CAS代理互斥量,偏向所是全去掉都不要了。锁偏向于获得它的线程,会在Mark Work中存储获得它的线程id,每次发现还是这个线程id就什么都不需要做,直接放行。如果产生竞争,可能重偏向或膨胀为轻量级锁。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值