玩转java并发-synchronized

synchronized实现原理

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

  1. 普通同步方法-锁是当前实例对象
  2. 静态同步方法-锁是当前类对象
  3. 同步方法块-锁是当前代码块

当一个线程访问同步代码块时,它首先是需要得到锁才能执行同步代码,当退出或者抛出异常时必须要释放锁,那么它是如何来实现这个机制的呢?

	public synchronized void test01(){
		
	}
	
	public void test02() {
		synchronized (this) {
			
		}
	}

同步代码块:monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁

同步方法:synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。(摘自:http://www.cnblogs.com/javaminer/p/3889023.html)

Java对象头

synchronized用的锁是存在java对象头中的,具体位置是对象头中的Mark Word。
Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。
对象头的存储结构:
在这里插入图片描述考虑虚拟机的空间效率,Mark Word复用自己的存储空,根据运行状态的改变而改变。
在这里插入图片描述

Monitor

Monitor可以理解为一种同步工具,或者是同步机制,常被描述为一个对象。所有的java对象都可以看作Monitor。在java的设计中,每个对象一创建就带了一把看不见的锁,叫做Monitor锁或者是内部锁。
Monitor是线程私有的数据结构,每个线程都有一个Monitor record列表,同时还有一个全局的可用列表。每个被锁住的对象都会和monitor关联,同时monitor中有一个owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。

monitor结构:

在这里插入图片描述

  1. 初始化为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL
  2. EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程
  3. RcThis:表示blocked或waiting在该monitor record上的所有线程的个数
  4. Nest:用来实现重入锁的计数
  5. HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)
  6. Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。

synchronized是重量级锁,在jdk1.6之后, JVM采用了一些优化手段

锁优化

锁主要存在四种状态:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态

自旋锁

频繁地进行线程的阻塞和唤醒对CPU而言是负担很重的工作,引入自旋锁解决这一问题。
所谓自旋锁就是让线程等待一会,不会被立即挂起,看持有锁的线程是否会很快释放锁。执行一段无意义的循环(自旋)。自旋等待必须要有一个限度,否则就是浪费处理器时间,典型"占着茅坑不拉屎"。
自旋锁在JDK1.6之后默认开启,自旋次数默认为10次。

自适应自旋锁

自适应自旋锁也就意味着自旋的次数不再是固定的,由前一次在同一个锁上的自旋时间和拥有者的状态而定。线程如果自旋成功了,下次自旋次数会更多,反之更少。

锁消除

在某些情况下,JVM检测到原来同步的代码块不可能有共享数据竞争,这时JVM会对这些同步锁进行锁消除。

锁粗化

我们在使用同步锁的时候,应该尽量让同步块的作用小一些仅在共享数据区域的地方进行同步。但是如果进行一系列的加锁解锁操作,可能会造成不必要的性能损耗,因此引入锁粗化的概念。
锁粗化就是将连续的加锁和解锁操作捆绑在一起,扩展成一个范围更大的锁。

轻量级锁

引入轻量级锁的主要目的就是在没有多线程竞争的前提下,减少传统的重量级锁的产生的系统消耗。当关闭偏向锁功能或者多个线程竞争偏向锁导致升级为轻量级锁,则会尝试获取轻量级锁。

获取轻量级锁的实现
偏向锁

引入偏向锁的主要目的就是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径。

获取偏向锁的实现
重量级锁

重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值