synchronized关键字,原理以及相关的锁

哈哈,这又是一篇读后感式的博客了!!
原文见:https://www.cnblogs.com/lcplcpjava/p/6858135.html
https://blog.csdn.net/u013132758/article/details/80167995
synchronized关键字大家都不陌生,为保证多线程下资源的不被多个线程同时修改而制定的。
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

修饰一个方法注意:

1.synchronized关键字不能继承
2.定义接口方法时不能使用synchronized关键字
3.构造方法不能使用synchronized关键字,但是可以使用synchronized (this)来同步代码块。
这里写图片描述

二、synchronized原理详解

  1、首先理解几个锁概念:对象锁(也就重量锁),轻量锁,偏向锁

  A、对象锁(重量锁)

  在多线程环境下,大多数(后面会将为什么是大多数修饰)每个对象都会有一个monitor对象(关于monitor具体可以查看jdk api文档),这个对象其实就是上面我们解释synchronized关键字时对应的锁。这个对象锁负责管理所有访问该对象的线程,具体管理模型图可以参考下面的示意图:

这里写图片描述

对于访问该对象的线程,并发情况下,没有抢到锁(即对象访问权)的线程,会被monitor丢进list这个队列进行等候(当然,自旋锁情况例外,后面会具体讲解什么卵是自旋锁)。而所谓的公平锁与非公平锁,就是在notify唤醒list中的等候线程时,list中的线程是按照排队顺序获得锁还是一起抢该对象的锁,前者是公平的,先等候的线程先获得锁;后者则是不公平的,抢不抢到锁,和线程等待时间无关,与运气有关。

  B、轻量锁

  显然,重量锁对于每个对象都要维护一个monitor对象,开销肯定是挺大的,jvm对此进行了一些优化,这便是轻量锁出现的原因。

  轻量锁大概是这样一种概念:尽管程序是多线程环境,但是如果访问当前对象的,该对象并不会new一个monitor,而是在对象的头部用一个标识字段(貌似是两位的二进制数)表示锁,这个就是轻量锁。在线程尝试访问该对象时,该线程会将当前线程私有空间中的对应锁标识字拷贝到对象头部,如果修改成功,表示只有一条线程访问该对象该对象继续采用轻量锁;如果发现轻量锁已经被其他线程占有锁定,jvm会将轻量锁升级为重量锁。

  C、偏向锁

  偏向锁是比轻量锁更轻量的锁:当jvm发现当前程序是但线程运行时,变会对对象采取偏向锁,当然,一旦发现有synchronized多线程执行情形,jvm会把偏向锁上升为轻量锁。

  2、synchronized如何实现

  了解了上面提到的几个锁的概念后,理解synchronized实现原理就非常容易了。

  在多线程环境下,对象对应的monitor管理着需要访问该对象的所有线程,monitor中会有个变量存储synchronized获取次数,在一个线程的中的某个synchronized代码块获取monitor之后,该变量数加一,对于synchronized嵌套情形一样如此:有多少个synchronized,对应的变量值就是多少,对于其他线程如果想获得monitor,必须等到当前占有monitor线程的所有synchronized块均执行完,每执行完一个synchronized块,标识变量值减一,变为0时,其他线程就可以开始抢锁了。

  所以,所有的管理工作均由monitor完成,它是synchronized实现的核心。

  3、其他知识的补充

  A、自旋锁

    上面提到,monitor在管理每个阻塞线程的时候,会把它们放进一个队列里面,这是针对非自旋锁,当monitor被设置为自旋锁锁时,线程不会被挂起放进队列,而是在做循环,直到获取monitor的访问权。所以自旋锁有什么存在必要呢?其实,线程的挂起和唤醒的调度过程是需要耗费cpu的,当线程持有锁的时间非常短时,我们没必要将等待线程挂起,这样会导致线程的频繁挂起和唤醒,浪费了cpu资源。当然,自旋锁中线程在做循环时肯定也会消耗资源的,所以如何选择还是要衡量调度线程花费大还是线程原地循环花费大。

    另外,针对上面情形,还有一种锁叫做自适应自旋锁,它大概解决了上面提到的一些问题;

  B、自适应自旋锁

    自适应自旋锁其实就是在自旋锁的基础上加了个自旋数的计数:当循环了一定次数后,该线程还没有获取锁时,它就被丢进队列里挂起。

猜你喜欢

转载自blog.csdn.net/chenmeng911/article/details/81777694
今日推荐