【JVM】关于JVM,你需要掌握这些 | 一文彻底吃透JVM系列(11)

  • 如果获取对象失败了 , 那当前线程就要阻塞等待 , 知道对象锁被另外一个线程释放为止 。
  • 除了synchronized之外 , 还可以使用java.util.concurrent包中的重入锁(ReentrantLock)来实现同步 。ReentrantLock比synchronized增加了高级功能:等待可中断、可实现公平锁、锁可以绑定多个条件 。
    等待可中断:当持有锁的线程长期不释放锁的时候 , 正在等待的线程可以选择放弃等待 , 对处理执行时间非常长的同步块很有用 。
    公平锁:多个线程在等待同一个锁时 , 必须按照申请锁的时间顺序来依次获得锁 。synchronized中的锁是非公平的 。
    非阻塞同步互斥同步最大的问题 , 就是进行线程阻塞和唤醒所带来的性能问题 , 是一种悲观的并发策略 。总是认为只要不去做正确的同步措施(加锁) , 那就肯定会出问题 , 无论共享数据是否真的会出现竞争 , 它都要进行加锁、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作 。
    随着硬件指令集的发展 , 我们可以使用基于冲突检测的乐观并发策略 。先进行操作 , 如果没有其他线程征用数据 , 那操作就成功了;如果共享数据有征用 , 产生了冲突 , 那就再进行其他的补偿措施 。这种乐观的并发策略的许多实现不需要线程挂起 , 所以被称为非阻塞同步 。
    锁优化是在JDK的那个版本?JDK1.6的一个重要主题 , 就是高效并发 。HotSpot虚拟机开发团队在这个版本上 , 实现了各种锁优化:
    • 适应性自旋
    • 锁消除
    • 锁粗化
    • 轻量级锁
    • 偏向锁
    为什么要提出自旋锁?互斥同步对性能最大的影响是阻塞的实现 , 挂起线程和恢复线程的操作都需要转入内核态中完成 , 这些操作给系统的并发性带来很大压力 。同时很多应用共享数据的锁定状态 , 只会持续很短的一段时间 , 为了这段时间去挂起和恢复线程并不值得 。先不挂起线程 , 等一会儿 。
    自旋锁的原理?如果物理机器有一个以上的处理器 , 能让两个或以上的线程同时并行执行 , 让后面请求锁的线程稍等一会 , 但不放弃处理器的执行时间 , 看看持有锁的线程是否很快就会释放 。为了让线程等待 , 我们只需让线程执行一个忙循环(自旋) 。
    自旋的缺点?自旋等待本身虽然避免了线程切换的开销 , 但它要占用处理器时间 。所以如果锁被占用的时间很短 , 自旋等待的效果就非常好;如果时间很长 , 那么自旋的线程只会白白消耗处理器的资源 。所以自旋等待的时间要有一定的限度 , 如果自旋超过了限定的次数仍然没有成功获得锁 , 那就应该使用传统的方式挂起线程了 。
    什么是自适应自旋?自旋的时间不固定了 , 而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定 。
    • 如果一个锁对象 , 自旋等待刚刚成功获得锁 , 并且持有锁的线程正在运行 , 那么虚拟机认为这次自旋仍然可能成功 , 进而运行自旋等待更长的时间 。
    • 如果对于某个锁 , 自旋很少成功 , 那在以后要获取这个锁 , 可能省略掉自旋过程 , 以免浪费处理器资源 。
    有了自适应自旋 , 随着程序运行和性能监控信息的不断完善 , 虚拟机对程序锁的状况预测就会越来越准确 , 虚拟机也会越来越聪明 。

    经验总结扩展阅读