并发编程-同步机制 synchronized

同步机制

临界资源:多线程编程中,有可能会出现多个线程同时访问同一个共享(资源可以由多个线程同时访问)、可变(资源可以由多个线程同时访问)资源的情况,这个资源我们称之其为临界资源;这种资源可能是:对象、变量、文件等。
由于线程执行的过程是不可控的,所以需要采用同步机制来协同对对象可变状态的访问。

如何解决线程并发安全问题?
序列化访问临界资源。即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。
Java 中,提供了两种方式来实现同步互斥访问:synchronizedLock

如何实现跨方法的对象锁?
在需要加锁的方法上使用sun.misc.Unsafe#monitorEnter
在需要解锁锁的方法上使用sun.misc.Unsafe#monitorExit

对象内存结构

  • 对象头:比如 hash码,对象所属的年代,对象锁,锁状态标志,偏向锁(线程)ID,偏向时间,数组长度(数组对象)等
  • 对象实际数据:即创建对象时,对象中成员变量,方法等
  • 对齐填充:对象的大小必须是8字节的整数倍

对象内存结构

Java锁体系

Java锁体系

对象头MarkWord与锁的关系

MarkWord

锁膨胀升级

JDK1.6版本之后对synchronized的实现进行了各种优化,如自旋锁、偏向锁和轻量级锁,并默认开启偏向锁。
开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
关闭偏向锁:-XX:-UseBiasedLockin

无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级到重量级锁,但是锁的升级是单向不可逆的。

锁粗化

有效地合并多个相邻的加锁代码块,因此可以减少加锁的成本。
如以下代码:

int i = 0;
synchronized (this){
    i++;
}
synchronized (this){
    i++;
}
synchronized (this){
    i++;
}

等同于

int i = 0;
synchronized (this){
    i++;
    i++;
    i++;
}

锁消除

消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间。

synchronized

JVM内置锁,无需手动加锁解锁,JVM会自动加锁解锁。

synchronized内置锁是一种对象锁(锁的是对象而非引用),作用粒度是对象,可以用来实现对临界资源的同步互斥访问,是可重入的。

加锁的方式

  • 同步实例方法,锁是当前实例对象
  • 同步类方法,锁是当前类对象同步代码块,锁是括号里面的对象
  • 同步代码块,锁是括号里面的对象

synchronized原理

JVM内置锁通过synchronized使用,通过内部对象Monitor(监视器锁)实现,基于进入(monitorEnter)与退出(monitorExit)Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex lock(互斥锁)实现,它是一个重量级锁,性能较低。
在这里插入图片描述

ObjectMonitor

每一个java对象都有一个监视器(monitor对象),jvm是这样定义ObjectMonitor的:

// initialize the monitor, exception the semaphore, all other fields
// are simple integers or pointers
ObjectMonitor() {
    _header       = NULL; //对象头
    _count        = 0; //加锁次数,重入时需要用到
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;  //ObjectMonitor对象的线程持有者
    _WaitSet      = NULL;  //_WaitSet 记录处于wait状态的线程
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;  //_EntryList 记录处于block状态的线程
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

修饰非静态方法

此时锁是加在this当前类对象上的,若想要实现线程安全控制,bean(由容器管理)的作用域必须是singleton

修饰静态方法

加锁在类对象上。

逃逸分析

面试提问:实例对象存储在哪?
如果实例对象存储在堆区时:实例对象内存存储在堆区,实例的引用存在栈中,实例的元数据存放在方法区。
当实例对象有线程逃逸行为时,实例对象不是存储在堆区的。

通过vm运行参数关闭逃逸分析

-XX:-DoEscapeAnalysis

通过vm运行参数开启逃逸分析

-XX:+DoEscapeAnalysis

遗留问题:JIT即时编译如何对代码进行逃逸分析?

证明:
我们使用循环去实例化五十万个对象。

当关闭逃逸分析时,我们使用jmap -histo 进程ID命令可以很清楚的看到实例对象个数为500000
关闭逃逸分析时实例对象数量

而当逃逸分析开启时,这个数量仅有56378个,此时实例对象存储在线程栈空间中
开启逃逸分析时实例对象数量

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值