当前位置: 首页 > news >正文

JAVA:synchronized总结

用法

修饰实例方法

将整个实例方法标记为同步方法,锁对象是当前实例。

修饰代码块

可以指定任意对象作为锁,使代码块在获取指定对象的锁后执行

修饰静态方法

将静态方法标记为同步方法,锁对象为类的 class 对象

 特性

原子性

保证操作的原子性,不可被中断

可见性

确保线程对共享变量的修改能及时被其他线程看到

有序性

禁止指令重排序,保证代码块内操作按照顺序执行

锁类型与升级过程

乐观锁VS悲观锁

悲观锁

 假设总是出现最坏的情况,在获取数据总担心其他线程修改数据,因此,在每次获取数据时都会   加锁,这样其他线程再想拿到数据只会阻塞等待,直到这个线程拿到锁。

乐观锁

假设拿数据一般不会发生并发冲突问题,直到在进行数据更新的时候才再考虑是否有并发冲突的问题。

synchronized 初始使用时采取乐观锁策略,如果检测到锁竞争比较频繁的时候会自动切换悲观锁策略

自旋锁VS挂起等待锁

挂起等待锁

线程在获取锁失败后就会进入阻塞状态,直到持有锁的线程释放锁以后才能获取到锁继续执行。 

自旋锁 

 线程在获取锁失败以后就会进入阻塞状态,放弃CPU,需要很久才能被再次调度。

其实大部分情况下获取锁失败以后,没多久再进行获取锁操作就能获取成功;因此就出现了自旋锁。

自旋锁就是相当于循环操作,一直尝试获取锁,直到获取到为止。

自旋锁是一种典型的 轻量级锁

优点:不涉及到线程阻塞与调度,没有放弃CPU,一旦锁被释放立即就能获取到锁。

缺点:如果其他线程持有锁的时间很久就会持续消耗CPU资源

重量级锁VS轻量级锁

在上文我们了解到锁有原子性的特征,锁的原子性追根溯源是由CPU提供的;CPU又提供了原子操作指令,操作系统又根据CPU的原子操作指令提供了 mutex 互斥锁,JVM 又根据操作系统的互斥锁提供了 synchronized 与 ReentrantLock 等关键字和类。

注:synchronized 不仅仅封装了 mutex 互斥锁,还实现了其他功能。

重量级锁

在某个线程访问同步代码块时,它会向操作系统请求获取互斥锁,如果互斥锁可用,线程将获取锁继续执行;如果不可用,线程就会进入操作系统的阻塞队列中等待被唤醒。加锁机制重度依赖 mutex ,成本较高

涉及了大量内核态用户态的切换以及很容易就引发了线程调度

轻量级锁

轻量级锁的加锁机制尽可能的不使用 mutex ,尽量在用户态代码完成,实在不行再调用 mutex 

少量内核态用户态切换,不易引发线程调度

synchronized 一开始是轻量级锁,如果锁竞争太激烈就会变成重量级锁

(用户态VS内核态

用户态是指应用程序的运行状态;在用户态下,程序只能访问有限的资源,例如自身的存储空间,并且只能执行非特权指令,类似普通的算术运算、逻辑运算等

内核态是指操作系统内核运行状态;在内核态下程序可以访问系统所有的资源,并且能够执行特权指令)

公平锁VS非公平锁

公平锁

多个线程获取锁时,会按照线程请求锁的先后顺序进行获取,就像排队一样,先到先得。

非公平锁

 不考虑线程请求的先后顺序,只要锁一释放其他等待获取锁的线程就都有机会抢占锁。

注意:操作系统对线程的调度是随机的,锁就是非公平锁,如果要实现公平锁,通常通过维护一个等待队列来实现。

synchronized 是非公平锁

可重入锁VS不可重入锁

可重入锁

可重入锁允许同一个线程在未释放锁的情况下多次获取该锁

不可重入锁

不可重入锁不允许同一个线程在未释放锁的情况下多次获取该锁

java 中只要以ReentrantLock 开头命名的锁都是可重入锁,JDK中所有的Lock的实现类也都是可重入锁,包括 synchronized 也是可重入锁

而在Linux 中的 mutex 是不可重入锁

读写锁

读写锁是一种用于多线程环境下的同步机制,它把对共享资源的访问划分为读操作与写操作。允许多个线程同时进行读操作,但是在写操作时会阻塞其他线程

其中,读操作与读操作之间不互斥,读操作与写操作之间互斥,写操作与写操作之间互斥

synchronized 不是读写锁

结合以上,我们可以看出synchronized 

  • 开始是乐观锁,锁竞争激烈会变成悲观锁
  • 开始是轻量级锁,锁被持有的时间很长就会变成重量级锁
  • 实现轻量级锁时大概率用的是自旋锁策略
  • 是不公平锁
  • 是可重入锁
  • 不是读写锁

加锁的工作过程 

JVM把synchronized 锁分为 无锁、偏向锁、轻量级锁、重量级锁

 偏向锁不是真的加锁,先做一个标记,如果存在其他线程竞争该锁,再进行真正加锁操作;如果没有,就不加了避免了加锁解锁的开销

锁消除

无锁,程序员写了加锁的代码,但其实是单线程的代码,无需加锁,JVM+编译器就判断是否可以进行锁消除,如果可以,就直接消除。

例如:

StringBuffer 中的 append 操作就进行了加锁,但是我在单线程中使用了它,就会进行锁消除操作。

 锁粗化

一段代码逻辑中出现多次加锁解锁操作,JVM+编译器就会自动进行锁粗化

相关文章:

  • 大模型核心运行机制
  • C语言中的宏
  • Prometheus参数配置最佳实践
  • P1032 [NOIP 2002 提高组] 字串变换
  • shell脚本变量详解
  • 【WebApi】YiFeiWebApi接口安装说明
  • python: union()函数用法
  • uniapp+vue3开发项目之引入vuex状态管理工具
  • 内存泄漏系列专题分析之十三:高通相机CamX内存泄漏内存占用分析--Camx pipeline的ION内存拆解方法
  • 从 Vue3 回望 Vue2:响应式的内核革命
  • 【bag of n-grams】 N-gram词袋模型 简介
  • 已情感分析入门学习大模型-初级篇
  • 进程与线程:09 进程同步与信号量
  • QLineEdit增加点击回显功能
  • Python 字典键 “三变一” 之谜
  • WebGIS 开发中的数据安全与隐私保护:急需掌握的要点
  • 带格式的可配置文案展示
  • 典籍指数问答模块回答格式修改
  • 深入浅出:C++数据处理类与计算机网络的巧妙类比
  • 嵌入式学习--江协51单片机day5
  • 国台办:台湾自古属于中国,历史经纬清晰,法理事实清楚
  • 金正恩观摩朝鲜人民军各兵种战术综合训练
  • 王毅谈中拉命运共同体建设“五大工程”及落实举措
  • 今天北京白天气温超30℃,晚间为何下冰雹?
  • 上海与世界|环城生态公园带是上海绿色发展新名片
  • 上海证监局规范辖区私募经营运作,6月15日前完成自评自纠