面试必备之synchroinzed原理

面试必备之synchroinzed原理

先来看一个例子:
image.png
并发叫号:并发量比较大的时候会出现:跳号、重号、超过最大值。
我们可以使用synchroinzed实现同步机制,synchroinzed具有独占性、排他性、可见性的特性。
synchroinzed解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

概念

是利用锁的机制来实现同步的。
锁机制有如下两种特性:
互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。
可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。

synchronized的用法

根据修饰对象分类

1、同步方法
(1) 同步非静态方法

Public synchronized void methodName(){
……
}

(2) 同步静态方法

Public synchronized static void methodName(){
……
}

  1. 同步代码块
synchronized(this|object) {}
synchronized(.class) {}
Private final Object MUTEX =new Object();
Public void methodName(){
 Synchronized(MUTEX ){
 ……
}
}

根据获取的锁分类

  1. 获取对象锁
synchronized(this|object) {}

修饰非静态方法
在 Java 中,每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。

  1. 获取类锁
synchronized(.class) {}

修饰静态方法
在 Java 中,针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁。
在 Java 中,每个对象都会有一个 monitor 对象,监视器。
1、某一线程占有这个对象的时候,先monitor 的计数器是不是0,如果是0还没有线程占有,这个时候线程占有这个对象,并且对这个对象的monitor+1;如果不为0,表示这个线程已经被其他线程占有,这个线程等待。当线程释放占有权的时候,monitor-1;
2、 同一线程可以对同一对象进行多次加锁,+1,+1,重入性

synchroinzed原理分析

线程堆栈分析(互斥)

Jconsole工具
image.png
image.png
Jstack pid命令

image.pngJVM指令分析

Javap -V 反编译,到 .class 文件目录下,命令java -v 文件名
image.png
可以看到,monitorenter、monitorexit实现对代码块的加锁,配合使用,会有两个monitorexit,一个是正常出口,另一个是异常出口。
Monitorexit,计数器减一,为0时为解锁。
monitor:
(1) 0----lock;
(2)重入;
(3) monitor一个线程占有,其他线程请求时会进入BLOCK,直到monitor为0;
(4)monitorexit monitor-1,直到为0时解锁。

Javap -v
image.png
以上是代码块的加锁monitorenter和monitorExit配合使用

对方法的加锁,是 ACC_SYNCHRONIZED,表示这是一个同步方法、互斥方法。
image.png
1.6JDK以前重量锁

使用synchronized注意的问题:

  1. 与moniter关联的对象不能为空
  2. synchronized作用域太大
  3. 不同的monitor企图锁相同的方法
  4. 多个锁的交叉导致死锁

Java虚拟机对synchronized的优化

1.偏向锁 01

在对象第一次被某一线程占有的时候,是否偏向置1,锁标记置01,写入线程号。当其他线程访问时,会竞争,如果成功获取锁,很多被第一次占有它的线程获取的次数多;如果失败升级为轻量级锁。
为了改进这种现象,有CAS算法(compare and set),竞争不激烈时使用。

2.轻量级锁 00

线程交替适用,互斥性不是很强。CAS失败,00

3.重量级锁 10

强互斥,等待时间长。
通过CAS算法,实现锁升级。

用户线程和核心线程,转换时间非常耗时,比如2—>3的转换。
自旋锁:竞争失败的时候,不是马上转化级别,而是执行几次空循环。
锁消除:JIT在编译的时候,把不必要的锁去掉了,比如 int i = 0,这个本身具有原子性,没必要加锁,所以编译的时候就会去掉。

对象头与monitor
一个对象实例包含:对象头、实例变量、填充数据
对象头:加锁的基础
实例变量:属性变量的信息
image.png

CAS算法

(Compare And Swap)比较交换。其实现方式是基于硬件平台的汇编指令,在intel的CPU中,使用的是cmpxchg指令,也就是说CAS是靠硬件实现的,从而在硬件层面提升效率。乐观锁,总是认为是线程安全的,不怕别的线程修改变量,如果修改了我就再重新尝试。悲观锁:总是认为线程不安全,不管什么情况都进行加锁,要是获取锁失败,就阻塞。
优点
这个算法相对synchronized是比较“乐观的”,它不会像synchronized一样,当一个线程访问共享数据的时候,别的线程都在阻塞。synchronized不管是否有线程冲突都会进行加锁。由于CAS是非阻塞的,它死锁问题天生免疫,并且线程间的相互影响也非常小,更重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,所以它要比锁的方式拥有更优越的性能。
实现思想在线程开启的时候,会从主存中给每个线程拷贝一个变量副本到线程各自的运行环境中,CAS算法中包含三个参数(V,E,N),V表示要更新的变量(也就是从主存中拷贝过来的值)、E表示预期的值、N表示新值。
image.png
实现过程
假如现在有两个线程t1,t2,,他们各自的运行环境中都有共享变量的副本V1、V2,预期值E1、E2,预期主存中的值还没有被改变,假设现在在并发环境,并且t1先拿到了执行权限,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试,然后t1比较预期值E1和主存中的V,发现E1=V,说明预期值是正确的,执行N1=V1+1,并将N1的值传入主存。这时候贮存中的V=21,然后t2又紧接着拿到了执行权,比较E2和主存V的值,由于V已经被t1改为21,所以E2!=V,t2线程将主存中已经改变的值更新到自己的副本中,再发起重试;直到预期值等于主存中的值,说明没有别的线程对旧值进行修改,继续执行代码,退出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瓜尔佳敏敏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值