synchronized原理及1.6之后的锁升级优化

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了synchronized原理及1.6之后的锁升级优化相关的知识,希望对你有一定的参考价值。

参考技术A 偏向所锁,轻量级锁及重量级锁

偏向所锁,轻量级锁都是乐观锁,重量级锁是悲观锁。

一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个

线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将

对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。

一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,( 偏向锁就是这个时候升级为轻量级锁的 )。如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。

轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

ReentrantLock原理,ReentrantLock和synchronized区别

@[toc]

ReentrantLock原理

重入锁ReentrantLock,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁,而不会造成自己阻塞自己。重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞。
ReentrantLock虽然没能像synchronized关键字一样支持隐式的重进入,但是在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。
除此之外,该锁的还支持获取锁时的公平和非公平性选择。实际上,公平的锁机制往往没有非公平的效率高,但是,并不是任何场景都是以TPS作为唯一的指标,公平锁能够减少“饥饿”发生的概率,等待越久的请求越是能够得到优先满足。
公平性锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平性锁虽然可能造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量。
在Java里一共有两类锁, 一类是synchornized同步锁,还有一种是JUC里提供的锁Lock,Lock是个接口,其核心实现类就是ReentrantLock。
ReentrantLock主要利用CAS+AQS队列来实现 ,主要是采用自旋锁,循环调用CAS操作来实现加锁,避免了使线程进入内核态的阻塞状态。
CAS:Compare and Swap,比较并交换。CAS有3个操作数:内存值V、预期值A、要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。该操作是一个原子操作,被广泛的应用在Java的底层实现中。在Java中,CAS主要是由sun.misc.Unsafe这个类通过JNI调用CPU底层指令实现

ReentrantLock和synchronized区别

可重入性:两者的锁都是可重入的,差别不大,有线程进入锁,计数器自增1,等下降为0时才可以释放锁
锁的实现:synchronized是基于JVM实现,操作系统级别(用户很难见到,无法了解其实现),ReentrantLock是JDK实现的,可以查到到对应实现的源码。
公平锁:synchronized只能是非公平锁。而ReentrantLock可以实现公平锁和非公平锁两种。
超时设置:synchronized不能设置超时,而Lock可以设置超时。
中断等待:synchronized不能中断一个等待锁的线程,而Lock可以中断一个试图获取锁的线程。
性能区别:在最初的时候,二者的性能差别差很多,当synchronized引入了偏向锁、轻量级锁(自选锁)后,二者的性能差别不大,官方推荐synchronized(写法更容易、在优化时其实是借用了ReentrantLock的CAS技术,试图在用户态就把问题解决,避免进入内核态造成线程阻塞)
功能区别

  • 便利性:synchronized更便利,它是由编译器保证加锁与释放,不会产生死锁。ReentrantLock是需要手动释放锁,所以为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。
  • 锁的细粒度和灵活度,ReentrantLock优于synchronized

ReentrantLock独有的功能

  • 可指定是公平锁还是非公平锁,所谓公平锁就是先等待的线程先获得锁
  • 提供了一个Condition类,可以分组唤醒需要唤醒的线程
  • 提供能够中断等待锁的线程的机制,lock.lockInterruptibly()

    ReentrantLock 和synchronized应该怎么选?

    从上边的介绍,看上去ReentrantLock不仅拥有synchronized的所有功能,而且有一些功能synchronized无法实现的特性。性能方面,ReentrantLock也不比synchronized差,那么到底我们要不要直接使用ReentrantLock放弃使用synchronized呢?答案是不要这样做。
    J.U.C包中的锁定类是用于高级情况和高级用户的工具,除非说你对Lock的高级特性有特别清楚的了解以及有明确的需要,或这有明确的证据表明同步已经成为可伸缩性的瓶颈的时候,否则我们还是继续使用synchronized。相比较这些高级的锁定类,synchronized还是有一些优势的,比如synchronized不可能忘记释放锁。还有当JVM使用synchronized管理锁定请求和释放时,JVM在生成线程转储时能够包括锁定信息,这些信息对调试非常有价值,它们可以标识死锁以及其他异常行为的来源。在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者轮询锁。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。总之,如果需要实现ReenTrantLock的三个独有功能时,就选择使用ReenTrantLock, 通常情况下synchronized就能够满足了,而且使用起来简单,由JVM管理,不会产生死锁。

以上是关于synchronized原理及1.6之后的锁升级优化的主要内容,如果未能解决你的问题,请参考以下文章

synchronized 锁升级过程

Java synchronized关键字的底层实现以及锁升级优化的原理一万字

Java synchronized原理

Synchronized优化

synchronized 底层如何实现?啥是锁的升级,降级

java 偏向锁轻量级锁及重量级锁synchronized原理