并发编程-synchronise系列

并发编程-synchronise系列

前言

synchronise一种常见的解决并发问题最常用的方式,面试对于synchronise考察主要在于其原理实现,如何使用等方面,以及和lock对比等问题,我们一起看来下下面几个问题,小伙伴们是否能回答上来

  • Synchronise是如何保证线程安全
  • Synchronize如何使用的,有什么问题
  • 什么是锁升级和降级
  • synchronized与lock的区别,synchronized与volatile的区别,synchronized底层原理
  • synchronized修饰方法和代码块有什么区别?

使用

对象锁

​ 对象锁包括方法锁(默认锁对象为当前实例对象this)和同步代码块锁

方法锁修饰的是普通方法,锁对象默认为this

 public synchronized void method() {}

同步代码块锁

synchronized(实例化对象) {
  //这里是同步代码块
}

this对象

synchronized(this) {
  //这里是同步代码块
}
类锁

​ 类锁指的是synchronize修饰静态的方法或者指定锁对象为Class对象

修饰静态方法

 public static synchronized void method() {
 }

修饰指定锁对象为class对象

 synchronized(类名.class){
 }

同步原理

Synchronize监控

​ Synchronize实现加锁和释放锁主要是通过一个monitor对象来完成,主要设计两个指令monitorenter和monitorexit两个指令来完成,同一个时间内一个monitor锁只能被一个线程获得,每一个对象在同一时间内只能与一个monitor锁相关联。 同时一个对象在尝试获取和这个对象相关联的Monitor锁的所有权的时候,monitorenter指令会发生如下三种情况:

  • monitor计数器为0,意味着目前没有被获得,这个线程会立刻获取到锁,然后锁计数器+1,一旦被+1,别的线程在想获取,就需要等待
  • 如果这个monitor已经拿到了这个锁的所有权,又重入了这把锁,那么锁计数器就会累加,变为2,重入一次累加1
  • 锁已经被别的线程获取,进入阻塞状态,一直等到monitor的进入数为0,再重试尝试获取monitor的所有权

monitorexit指令:会释放对monitor(锁)的所有权,释放过程就是将锁的计数器减1

Synchronize和Lock

  1. Lock是一个接口,Synchronize是java里面的关键字
  2. Synchronize发生异常的时候,会主动释放掉自己占用的锁,因此不会导致死锁,但是Lock就不行了,我们使用时候如果没有主动unLock()释放锁,这会可能会造成死锁,一般开发过程中我们使用lock的时候会跟一个finally块去释放锁
  3. Lock可以让等待锁现象响应中断,Synchronize不行,Synchronize等待的线程会一直等待下去,不能够响应中断 ,只有拿到锁之后才会响应中断
    • 中断是线程调用interrupt()方法,ReenLock在获取锁之前和自旋等待判断是否应该中断,如果设置为中断状态就会显示抛错,Synchronise就不一样了,设置中断的的时候,只会设置中断状态设置为true,不会抛出异常(只有调用wait,sleep,join才会显示抛出InterruptedException异常)。
  4. Lock可以通过tryLock()判断自己是否获取到了锁,synchronise不行
  5. Lock可以实现公平锁,synchronise不保证公平性

Volatile和synchronise

  1. volatile修饰变量,synchronise修饰代码块和方法
  2. volatie不会加锁,不会造成线程阻塞,synchronise会对变量加锁,可能造成线程阻塞
  3. volatile只保证可见性,不保证原子性,synchronise可见和原子都能保证
  4. volatile标记变量不会被编译器优化,禁止指令重排,synchronise变量可以被优化

锁优化

JVM中monitorenter和monitorexit字节码依赖于底层操作系统Mutex lock 锁来实现,但是由于Mutex Lock需要将当前线程挂起并且从用户态切换成内核态来执行,会严重影响程序运行性能。在JDK1.6中引入很多优化,比如锁粗化,锁消除,轻量级锁,偏向锁,适应性自旋等技术减少锁操作开销。

锁类型

​ JDK1.6开始,对于Synchronize的实现机制进行了重大调整,增加了自适应CAS自旋,锁消除,锁粗化,偏向锁,轻量级锁优化策略,Synchronize同步锁一共有四种状态:无锁,偏向锁,轻量级锁,重量级锁,会随着竞争情况升级(只能升不能降)

自旋锁

​ 线程的阻塞和唤醒需要CPU从用户态转化为核心态,频繁的阻塞和唤醒会影响到系统的并发,同时在很多应用上面,对象锁的锁状态只会持续很短一段时间,为了这很短的时间频繁阻塞和唤醒线程也不值当,这里就引入了自旋锁。自旋锁就是一个线程尝试获取某一个锁的时候,如果锁被其他线程占用,就会一直循环检测锁是否被释放,而不是将线程挂起获取进入睡眠状态。

优点/缺点:如果等待持有锁的线程很快释放锁,自旋效率就很好,如果太长就会一直占用CPU处理器的时间,浪费系统资源,一般会对自旋的时间有个限制,如果没有获取到锁,线程就会被挂起。

自适应自旋锁

在JDK1.6引入了自适应自旋锁,线程如果这一次自旋成功,那么下一次自旋的次数就会更多,因为虚拟机认为上次你都成功了,下一次增加次数肯定也会成功。

锁消除

​ 通过逃逸分析发现代码没有资源竞争的可能,但是代码自己加了锁,虚拟机编译的时候就会直接去掉这个锁。比如在操作字符串进行相加的时候,由于String是一个不可变类,对字符串的连续操作是通过生成新的String来进行的,在JDK1.5之前会使用StringBuffer对象连续进行append()操作,查看源码可以知识,StringBuffe的StringBuffer方法加synchronise的。在JDK1.5之后,转化为StringBuide对象进行连续append()操作,这个操作不加synchronise,因为这是局部使用,不存在资源竞争

public static String lockElimination(String s1, String s2, String s3) {
    String s = s1 + s2 + s3;
    return s;
} 
锁粗化

锁粗化就是将连续多个加锁,解锁操作连接在一起,扩展成一个范围更大的锁,还有个例子就是在for循环中加synchronize关键字,这里也会扩大锁的范围避免频繁拿到锁进行上下文切换

/**
* StringBuffer的append操作会加Synchronize关键字,一个append加一个,这谁能受得了,JVM会优化成一个synchronise操作
*/
public static String lockCoarsening(String s1, String s2, String s3) {
         StringBuffer sb=new StringBuffer();
           sb.append("1");
           sb.append("2");
           sb.append("3");
   				 return sb.toString();
}
轻量级锁/偏向锁

这两种锁既是一种优化策略,也是一种膨胀过程,引入轻量级锁的主要目的是:在没有多线程竞争的前提下,减少传统重量级使用操作系统互斥而产生的性能消耗。关闭偏向锁或者多线程竞争偏向锁的时候会导致偏向锁升级为轻量级锁。

偏向锁

偏向锁是为了解决大多数情况下虽然加了锁,但是没有竞争的发生,甚至是同一个线程反复获得这个锁。主要过程如下

首先JVM需要设置可用偏向锁,进程访问同步代码块并且获取到锁的时候,会在对象头和栈帧的锁记录里面存储取到的偏向锁的线程ID,下次你在来的时候首先检查对象头MarkWord是不是存储的是这个线程的ID,如果是,直接进去,不是的话,会出现两种情况

  • 对象的偏向锁标志位为0(当前不是偏向锁),说明发生了竞争,锁已经膨胀为轻量级锁,使用CAS尝试获取锁
  • 偏向锁标志位为1,说明还是偏向锁,但请求的线程不是原来那个,这个时候使用CAS尝试会把对象头偏向锁从原来的线程指向目前请求的线程。
轻量级锁

轻量级锁是偏向锁的产物,线程同步的时候,会在当前线程的栈帧记录中创建一个锁记录(Lock Record)的空间,JVM请求的时候会使用CAS将锁对像头Make Word拷贝到锁记录里面。如果CAS成功,就会将对象头里面的标准为01更新为00获取到锁,失败的话说明线程出现竞争,锁膨胀为重量级锁

重量级锁

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

闲谈

感觉有帮助的同学还请点赞关注,这将对我是很大的鼓励~,公众号有自己开始总结的一系列文章,需要的小伙伴还请关注下个人公众号程序员fly,希望能一起成长。

扫码关注一下呗

巨人肩膀

https://www.cnblogs.com/aspirant/p/11705068.html

https://www.cnblogs.com/aspirant/p/11470858.html

https://www.pdai.tech/md/java/thread/java-thread-x-key-synchronized.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值