Synchronized重量级锁的原理分析

Synchronized

前言

今天又重新看了下Synchronized的原理, 以前觉得这不就是八股文吗?背就完了,最近看了concurrenthashmap的源码以后,加深了对cas的理解之后,又有了新的收获,才发现自己真的是见识太浅薄了,该掌嘴啊!学这些东西,尽量细节, 是为了在以后工作中,能够复用前人这些优秀的设计思路。所以记录下自己学习的内容和思考。写完这篇文章之后,你要问问你自己以下问题。

  • Synchronized中重量级锁的实现原理是什么?
  • 实现过程中那里用到了CAS原理 ?
  • 线程在获取这把锁的时候,到底采用的是何种算法
  • 如果叫你自己用java实现一个synchronized锁,你会如何优化?

原理

下面我写的内容是Synchronized重量级锁的实现,轻量级锁, 偏向锁不在其中。

锁是什么?

我们知道,一般来说,我们的重量级锁对象一般是声明一个Object. 其实,任何对象都可以锁对象,因为我们关注的是对象头的信息。但是最好还是使用Object, 怕的是其他对象直接重写Object的一些方法,比如notify(),wait()方法。

Monitor是什么?

Monitor 是操作系统实现的一个对象,Java中每个锁都会关联一个Monitor对象,由此来实现Synchronized。

Monitor的大致结构如下

  • owner 用来关联java中的线程,表示这个Monitor当前归哪个线程所有
  • entrylist 队列,用来存在获取不到锁时的线程
  • waitset 当调用了wait方法后,线程会放到这个里面

加锁过程分析

// thread-0
Object lock = new Object();
synchronized(lock) {
    // code ...
}
  • 当代码执行到synchronized那一行时,将对象头的markword替换为monitor对象

  • thread-0首先看owner是否为null, 如果为null, 就把owner 设置为当前线程对象,实现了线程和owner的绑定, 加速成功。

    请注意,这里有一个很关键的东西,就是比较并设置 , 当你看到这个字眼时,应该是马上想到这必须是原子操作,不然要出问题。最简单的例子就是thread-0刚刚比较了,发现owner为null, 这时候操作系统发生上下文切换,thread-1也发现这个owner是null, 于是设置owner为1, 这时thread0又分到时间片,将owner改为0, 问题就出现了。按道理讲,我们只允许一个线程设置成功,现在两个线程都成功了。原因在于这里的比较并交换不是原子操作,比较之后,容易发生线程的上下文切换,导致另一个现场设置成功。所以这个地方是cas的比较并替换。

  • 执行完临界区的代码后,开始解锁,将owner设置为null, 这个解锁不用CAS, 因为没人来和我争。就像是你这个渣男和一堆屌丝追求女神,只有一个人可以追到,你追到了,最后分手的时候别人都不会来抢,分手权只有你有!

  • 锁对象的markword进行复原。

锁竞争分析

  • thread-1执行到synchronized代码块的时候,通过对象的markword找到monitor对象
  • 通过CAS设置owner为当前线程对象,发现owner不为null, 此时被thread-0占着。
  • 加锁失败,当前线程放到entrylist中进行阻塞。
  • thread-0释放锁以后,把monitor里面所有的thread唤醒去争抢锁,通过CAS去抢这把锁。

开头的问题自己的一点思考

  • Synchronized中重量级锁的实现原理是什么?

    本质上就是锁对象关联monitor, 我们常说java的线程和操作系统的线程是一对一绑定的,通过观察字节码也可以知道,是通过monitorenter和monitorexit来处理的。同时也要知道,是如何防止死锁的,主要是做了异常检测,如果发生了异常会进行检测。好比加锁时我们必须把业务代码放到try 最后用finally来解锁。如果担心客户端不遵循这种规则,可以定义业务逻辑接口,只用业务方实现接口即可,不用关心加锁,解锁流程。

  • 实现过程中那里用到了CAS原理 ?

​ 在判断owner是否null, 并设置为当前线程这个地方用来CAS, 加锁的时候用了CAS, 解锁的时候不用。

  • entrylist里的线程在获取这把锁的时候,到底采用的是何种算法

    entrylist里的线程被唤醒后,采用CAS去抢锁,这是不公平的,属于抢占式调度。其他算法还有先来先服务,最短时间优先等等。

  • 如果叫你自己用java实现一个synchronized锁,你会如何优化?

​ 如果叫我用java实现一个的话,我会对CAS这个地方做优化,我们知道线程上下文的切换,会从用户态转向内核态,保存现场,造成一定的时间开销,如果线程执行的逻辑代码本身很短的话,我们就可以自旋几次,从而不用直接阻塞。

posted @ 2021-08-01 11:30  FizzPu  阅读(260)  评论(0编辑  收藏  举报