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

并发编程之线程安全

并发编程之线程安全

JAVA中锁的概念

自旋锁: 当线程去争抢对象的锁时,该对象锁已经被占用,当前线程就会循环等待,不断的去判断锁是否能够被获取,如果不能被获取,直到成功获取锁才会退出循环;

乐观锁:在修改数据时如果发现数据和之前获取的不一致,则读最新数据,修改后重试修改;

悲观锁:假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁;

独占锁(写):给资源加上写锁,线程可以修改资源,其他线程不能再加锁; (单写)

共享锁(读):给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁; (多读)

可重入锁:线程拿到一把锁之后,可以自由进入同一把锁所同步的其他代码。

不可重入锁:线程拿到一把锁之后,不可以自由进入同一把锁所同步的其他代码,还需要重新获得锁

公平锁:争抢锁的顺序,如果是按先来后到,则为公平。

非公平锁:争抢锁的顺序,不按先来后到,则为不公平。

几种重要的锁实现方式:synchronized、ReentrantLock、ReentrantReadWriteLock

synchronized

为什么使用synchronized

看以下实例:

当不使用synchronized关键字时,并发20个线程,同时每个线程分别调用demo.add方法100次,执行加法运算,期望结果为:2000,但是输出结果又出现“1979”的情况,如下:

当使用synchronized关键字时,同样并发20个线程,同时每个线程分别调用demo.add方法100次,执行加法运算,期望结果为:2000,结果都为:2000

public class Counter {// 假设i 是一个spring容器里面的对象,共享对象private int i = 0;private static Object lock = new Object();//不加synchronizedpublic void add() {i = i + 1;}//加synchronizedpublic void add() {synchronized(lock){i = i + 1;}}public static void main(String[] args) {for(int i = 0; i < 200; i++) {run();}//输出结果:/**200019792000*/for(int i = 0; i < 200; i++) {run1();}//输出结果:/**......20002000*/}//并发20个线程,同时调用demo.add方法执行加法运算public static void run() {Counter demo = new Counter();List<Thread> threads = new ArrayList<Thread>();for(int j =0;j<20;j++){Thread th = new Thread(()->{for(int i = 0; i < 100; i++) {demo.add();}});th.start();threads.add(th);}for(Thread t : threads) {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(demo.i);}//并发20个线程,同时调用demo.add1方法执行加法运算public static void run1() {Counter demo = new Counter();List<Thread> threads = new ArrayList<Thread>();for(int j =0;j<20;j++){Thread th = new Thread(()->{for(int i = 0; i < 100; i++) {demo.add1();}});th.start();threads.add(th);}for(Thread t : threads) {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(demo.i);}
}

synchronized使用

正确使用
  1. 用于实例方法,隐式的指定锁对象,就是调用实例方法的对象;
  2. 用于静态方法,隐式的指定锁对象,就是当前类对象class
  3. 用于代码块,需要显示指定锁对象,可以指定为调用实例方法的对象,或者自定义的(静态或成员)对象
实例
//1. 用于实例方法,隐式的指定锁对象,就是调用实例方法的对象;public synchronized void add() {i = i + 1;}
//2. 用于静态方法,隐式的指定锁对象,就是当前类对象classprivate static int i = 0;public static synchronized void add() {i = i + 1;}
//3. 用于代码块,需要显示指定锁对象,可以指定为调用实例方法的对象,或者自定义的(静态或成员)对象
//--------------第一种:this
public void add() {synchronized(this) {i = i + 1;}
}
//--------------第二种:自定义对象private Object lock = new Object();public void add() {synchronized(lock) {i = i + 1;}}
//--------------第三种:静态自定义对象private static Object lock = new Object();public void add() {synchronized(lock) {i = i + 1;}}

synchronized特性

可重入、独占、悲观锁

特殊优化 :

​ 锁消除(开启锁消除的参数:-XX:+DoEscapeAnalysis -XX:+EliminateLocks)

​ 锁粗化 JDK做了锁粗化的优化, 但我们自己可从代码层面优化

synchronized关键字,不仅可以实现同步,JMM中,synchronized可以保证可见性(结果不能被缓存)

synchronized原理

JVM运行时数据区

JVM运行时数据区包含:线程栈、堆、方法区,如下如:

image-20220123114558810

堆中Java对象

Java对象包含三部分:对象头、属性值、填充(字节对齐),如下图:

image-20220123123946848

image-20241031082750811

对象头Object Header

对象头包含:Mark Word 、Class Metadata Address、Array Length,如下图:

image-20220123124140460

Mark Word

image-20220123124722580

默认情况下JVM锁会经历:未锁定->偏向锁 -> 轻量级锁 -> 重量级锁 这四个状态

轻量级锁

在未锁定的状态下,可以通过CAS来抢锁,抢到的是轻量级锁

image-20231226232042478

线程栈开辟一个空间,存储当前锁定对象的Mark Word信息。

Lock record可以存储多个锁定的对象信息。

image-20241031083455320

使用CAS修改mark word完毕,加锁成功。则mark word中的tag进入 00 状态。

解锁的过程,则是一个逆向恢复mark word的过程

image-20241031083425204

重量级锁

轻量级锁中的自旋有一定的次数限制,超过了次数限制,轻量级锁升级为重量级锁

image-20231226232109084

image-20241031083640994

Monitor对象可以参考openjdk中的objectMonitor中的信息

openjdk\hotspot\src\share\vm\runtime\objectMonitor.cpp
openjdk\hotspot\src\share\vm\runtime\objectMonitor.hpp
openjdk\hotspot\agent\src\share\classes\sun\jvm\hotspot\runtime\ObjectMonitor.java

偏向锁

在JDK6 以后,默认已经开启了偏向锁这个优化,通过JVM 参数 -XX:-UseBiasedLocking 来禁用偏向锁若偏向锁开启,只有一个线程抢锁,可获取到偏向锁。

偏向标记第一次有用,出现过争用后就没用了。 -XX:-UseBiasedLocking 禁用使用偏置锁定,

偏向锁,本质就是无锁,如果没有发生过任何多线程争抢锁的情况,JVM认为就是单线程,无需做同步

(jvm为了少干活:同步在JVM底层是有很多操作来实现的,如果是没有争用,就不需要去做同步操作)

锁的升级过程

image-20231226232148986

锁升级的过程如下:

image-20220123131514275

同步关键字加锁原理

HotSpot中,对象前面会有一个类指针和标题,储标识哈希码的标题字以及用于分代垃圾收集的年龄和标记位

默认情况下JVM锁会经历:偏向锁 -> 轻量级锁 -> 重量级锁这四个状态

image-20231226232311538

参考来源:

https://www.cs.princeton.edu/picasso/mats/HotspotOverview.pdf

https://wiki.openjdk.java.net/display/HotSpot/Synchronization

wait/notify机制

wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。

notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。

注意:

​ 只能在synchronized关键字中使用,且调用wait、notify的对象与锁对象相同,否则会抛IllegalMonitorStateException异常。

image-20231226232443375

image-20241031084121753

参考来源

  • https://www.cs.princeton.edu/picasso/mats/HotspotOverview.pdf

  • https://wiki.openjdk.java.net/display/HotSpot/Synchronization

ify的对象与锁对象相同**,否则会抛IllegalMonitorStateException异常。

[外链图片转存中…(img-0rNr3psI-1747909233174)]

[外链图片转存中…(img-Z7ADnGzk-1747909233174)]

参考来源

  • https://www.cs.princeton.edu/picasso/mats/HotspotOverview.pdf

  • https://wiki.openjdk.java.net/display/HotSpot/Synchronization

  • 《深入理解Java虚拟机》

http://www.xdnf.cn/news/586711.html

相关文章:

  • 云原生安全 SaaS :从基础到实践
  • 驱动钛丝(SMA)的应用(5)汽车腰托气阀常见问题及解决方案
  • 101个α因子#23
  • 如何让 Agent 有计划地进行股票数据分析?——基于 DeepSeek 的实战应用
  • linux字符模式关闭光标
  • Linux操作系统:fork+exec进程创建
  • win11远程桌面设置60fps无效
  • 面试题 17.16. 按摩师
  • 软件开发的设计原则
  • 徐少春迎来AI的春天
  • spring中的BeanFactoryAware接口详解
  • 关于我对传统系统机构向大模型架构演进的认知
  • 无线网络优化配置:让你的Wi-Fi更快更稳
  • java: Can‘t generate mapping method with primitive return type
  • 高级SQL技巧:时序数据查询优化与性能调优实战
  • 天文数据处理:基于CUDA的射电望远镜图像实时去噪算法(开源FAST望远镜数据处理代码解析)
  • github cli主要用途,优势,和git的区别
  • PageHelper分页原理解析:从源码到MySQL方言实现
  • 基于开源AI大模型与智能硬件的零售场景服务创新研究——以AI智能名片与S2B2C商城小程序源码融合为例
  • [安全清单] Linux 服务器安全基线:一份可以照着做的加固 Checklist
  • 用Python和Backtrader库实现均值回归策略解析
  • 角度回归——八参数检测四边形RSDet
  • MIPI摄像头linux驱动开发步骤及说明
  • Python 数据分析基础
  • 差分探头匹配电容选择方法
  • [Linux]Linux多线程编程技术探讨(代码示例)
  • LeetCode[222]完全二叉树的节点个数
  • GraphPad Prism工作表的基本操作
  • python、R、shell兼容1
  • 深入解析Java泛型:从定义到实战应用