http://www.jianshu.com/p/5dbb07c8d5d5

原理

通常说的synchronized在方法或块上加锁,这里的锁就是对象锁(当然也可以在类上面),或者叫重量锁,在JVM中又叫对象监视器(Monitor),就是对象来监视线程的互斥。

先来回顾一下对象在堆里的逻辑结构:

 

对象在内存中的结构看这里》》

对象头里的结构大致如此:

 

其中Tag的2bit用来显示锁类型。通常我们说synchronized的对象锁,就是这里Tag=10时的monitor对象,这里的Monitor address就是这个monitor对象(就是重量锁)的地址。

当多个线程同时请求synchronized方法或块时,monitor会设置几个虚拟逻辑数据结构来管理这些多线程。下图是简化了的管理结构。

 

新请求的线程会首先被加入到线程排队队列中,线程阻塞,当某个拥有锁的线程unlock之后,则排队队列里的线程竞争上岗(synchronized是不公平竞争锁,下面还会讲到)。如果运行的线程调用对象的wait()后就释放锁并进入wait线程集合那边,当调用对象的notify()或notifyall()后,wait线程就到排队那边。这是大致的逻辑。

同时再看看线程的状态图

 

Blocked就是阻塞状态。

wait()和sleep()最大的不同在于wait()会释放对象锁,而sleep()不会!wait、sleep、yield区别如下:

 

似乎讲到这里,synchronized锁和wait()、notify()来实现多线程同步就完成了。

但是,自旋锁或自适应自旋锁:

因为线程阻塞后进入排队队列和唤醒都需要CPU从用户态转为核心态,尤其频繁的阻塞和唤醒对CPU来说是负荷很重的工作。同时统计发现,很多对象锁的锁定状态只会持续很短的一段时间,例如一个线程切换周期,这样的话在很短的时间内阻塞线程又很快唤醒线程显然不值得,所以引入了自旋锁概念。

所谓“自旋”,就monitor并不把线程阻塞放入排队队列,而是去执行一个无意义的循环,循环结束后看看是否锁已释放并直接进行竞争上岗步骤,如果竞争不到继续自旋循环,循环过程中线程的状态一直处于running状态。明显自旋锁使得synchronized的对象锁方式在线程之间引入了不公平。但是这样可以保证大吞吐率和执行效率。

不过虽然自旋锁方式省去了阻塞线程的时间和空间(队列的维护等)开销,但是长时间自旋也是很低效的。所以自旋的次数一般控制在一个范围内,例如10,50等,在超出这个范围后,线程就进入排队队列。

自适应自旋锁,就是自旋的次数是通过JVM在运行时收集的统计信息,动态调整自旋锁的自旋次数上界。

讲到这里似乎synchronized锁的过程更加丰满了。

不过synchronized在运行过程中不是一下子就到对象锁这个级别的,它根据线程竞争情况会经过几次升级变化。这里就出现了另外几种锁。

轻量锁和偏向锁

当多线程环境进入synchronized区域的线程没竞争时,JVM并不会马上创建对象锁,而是用轻量锁或偏向锁。

不过需要明确的是,轻量锁和偏向锁,都不能代替重量锁,只不过是在没有多线程竞争时,没必要用重量锁而无畏的消耗资源。但是一旦出现了多线程竞争时,synchronized区域的轻量锁或偏向锁都会立即升级为重量锁。

轻量锁或偏向锁使用的条件是进入synchronized区域时没有其他任何其他线程在使用。

这时线程t访问对象的synchronized区域时,对象头的标志位Tag状态为01,以及还有1位的偏向信息用于记录这个对象是否可用偏向锁。然后t在对象上申请轻量锁时,若偏向信息为0,表明当前对象还未加锁,或加过偏向锁(加过,注意是加过偏向锁的对象只能被同样的线程加锁,如果不同的线程想要获取锁,需要先将偏向锁升级为轻量锁,稍后会讲到),在判断对当前对象确实没有被任何其他线程锁住后,即可以在该对象上加轻量锁。

加轻量锁的过程很简单:在当前线程的栈帧(stack frame)中生成一个锁记录(lock record),这个锁记录比前面说的那个对象锁(管理线程队列的monitor)简单多了,它只是对象头的一个拷贝。然后把对象头里的tag改成00,并把这个栈帧里的lock record地址放入对象头里。若操作成功,那就完成了轻量锁操作。如果不成功,说明有线程在竞争,则需要在当前对象上生成重量锁来进行多线程同步,然后将Tag状态改为10,并生成Monitor对象(重量锁对象),对象头里也会放入Monitor对象的地址。最后将当前线程t排队队列中。

轻量锁的解锁过程也很简单就是把栈帧里刚才的那个lock record拷贝到对象头里,若替换成功,则解锁完成,若替换不成功,表示在当前线程持有锁的这段时间内,其他线程也竞争过锁,并且发生了锁升级为重量锁,这时需要去Monitor的等待队列中唤醒一个线程去重新竞争锁。

偏向锁是比轻量锁还轻量的锁机制。当synchronized区域长期都由同一个线程加锁、解锁时,jvm就用偏向锁来做,它的加锁解锁比轻量锁操作起来指令更加简化。不过一旦有其他线程使用synchronized区域,即使没有线程间竞争,也会把偏向锁升级为轻量锁,当然如果发生线程竞争就再升级为对象锁。

锁的公平与不公平:公平锁是指线程获得锁的顺序按照fifo的原则,先排队的先得。非公平锁指每个线程都先要竞争锁,不管排队先后,所以后到的线程有可能无需进入等待队列直接竞争到锁。

非公平锁虽然可能导致某些线程饥饿,但是锁的吞吐率是公平锁好几倍,synchronized是一个典型的非公平锁方案,而且没法做成公平锁。

文/hexter(简书作者)
原文链接:http://www.jianshu.com/p/5dbb07c8d5d5
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

synchronized锁自旋的更多相关文章

  1. synchronized锁自旋2

    http://www.infoq.com/cn/articles/java-se-16-synchronized 1 引言 在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它 ...

  2. Synchronized锁性能优化偏向锁轻量级锁升级 多线程中篇(五)

    不止一次的提到过,synchronized是Java内置的机制,是JVM层面的,而Lock则是接口,是JDK层面的 尽管最初synchronized的性能效率比较差,但是随着版本的升级,synchro ...

  3. 多线程之Synchronized锁的基本介绍

    基本介绍 synchronized是Java实现同步的一种机制,它属于Java中关键字,是一种jvm级别的锁.synchronized锁的创建和释放是此关键字控制的代码的开始和结束位置,锁是有jvm控 ...

  4. 015-线程同步-synchronized几种加锁方式、Java对象头和Monitor、Mutex Lock、JDK1.6对synchronized锁的优化实现

    一.synchronized概述基本使用 为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题. syn ...

  5. synchronized 锁优化

    synchronized 在jdk 1.7之前是重量级锁,独占锁,非公平锁.jdk1.7之后,synchronized引入了 偏向锁,自旋锁,轻量级锁,重量级锁 自旋锁 当线程在获取锁的时候,如果发现 ...

  6. 【从刷面试题到构建知识体系】Java底层-synchronized锁-1

    在技术论坛中,经常看到一种言论:面试造火箭,干活拧螺丝.我们平时写的大部分代码的确是CRDU,再提一个层次,也无非就是揉进去复杂一些的业务逻辑,把一堆的CRDU组合起来. 那么问题来了:我们提倡的研究 ...

  7. Java性能之synchronized锁的优化

    synchronized / Lock 1.JDK 1.5之前,Java通过synchronized关键字来实现锁功能 synchronized是JVM实现的内置锁,锁的获取和释放都是由JVM隐式实现 ...

  8. Synchronized锁升级

    Synchronized锁升级 锁的4中状态:无锁状态.偏向锁状态.轻量级锁状态.重量级锁状态(级别从低到高) 为什么要引入偏向锁? 因为经过HotSpot的作者大量的研究发现,大多数时候是不存在锁竞 ...

  9. 深入并发锁,解析Synchronized锁升级

    这篇文章分为六个部分,不同特性的锁分类,并发锁的不同设计,Synchronized中的锁升级,ReentrantLock和ReadWriteLock的应用,帮助你梳理 Java 并发锁及相关的操作. ...

随机推荐

  1. redux-actions源码解读

    一.什么是redux-actions redux-actions是一个简化action和reducer创建的一个封装库,里面有5个js文件, createAction.js handleAction. ...

  2. MemPool

    腾讯笔试题,设计内存池,alloc和free都是O(1). 和LRUCache类似,这里用了一个list表示可用的空间,用一个map来记录这块内存是否已分配,这样free的时候才可能O(1). cla ...

  3. 使用mutt+msmtp在Linux命令行界面下发邮件

    mutt命令执行了却发送不了邮件, 搜索了一会才明白原来是mutt需要一个发信的程序, msmtp刚好是一个不错的选择. 1. apt-get install mutt msmtp (安装过程中会弹出 ...

  4. Ubuntu基础命令

    显示历史命令的时间戳 echo 'HISTTIMEFORMAT="%F %T "' >> ~/.bashrc   解压文件/打包文件夹 tar -zxvf folder ...

  5. 通过Powershell开启RDP

    1) Enable Remote Desktop set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Ser ...

  6. Java发展史之Java由来

    Java:由Sun Microsystems公司于1995年5月推出的Java程序设计语言和Java平台的总称.Java语言是一种可以撰写跨平台应用软件的面向对象的程序设计语言,由当时任职太阳微系统的 ...

  7. fastjson生成json时Null属性不显示

    举个例子 生成JSON代码片段 Map < String , Object > jsonMap = new HashMap< String , Object>(); jsonM ...

  8. hrbust oj 1526+2028 树状数组

    冒泡排序中 如果一个数的后面的某个数和这个数不符合排序规则 那么这个数就会在未来的某次冒泡中与那个数进行交换 这里用到了 树状数组求逆序数的办法来做 需要注意的是2028并不可以改完数组大小后直接套1 ...

  9. [IT学习]一些有用的工具

    1.synctoy: http://www.microsoft.com/en-us/download/details.aspx?id=15155 2.fsutil https://technet.mi ...

  10. MySql练习+加源代码

    一.设有一个数据库,包括四个表:学生表(Student).课程表(Course).成绩表(Score)以及教师信息表(Teacher). 四个表的结构分别如表1-1的表(一)~表(四)所示,数据如表1 ...