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

Java 内存模型

Java 内存模型是什么

我们知道的 JVM 内存区域有:堆和栈,这是一种泛的分法,也是按运行时区域的
一种分法,堆是所有线程共享的一块区域,而栈是线程隔离的,每个线程互不共享。
线程不共享区域每个线程的数据区域包括程序计数器、虚拟机栈和本地方法栈,它
们都是在新线程创建时才创建的。
程序计数器( Program Counter Rerister
程序计数器区域一块内存较小的区域,它用于存储线程的每个执行指令,每个线程
都有自己的程序计数器,此区域不会有内存溢出的情况。
虚拟机栈( VM Stack
虚拟机栈描述的是 Java 方法执行的内存模型,每个方法被执行的时候都会同时创
建一个栈帧( Stack Frame )用于存储局部变量表、操作数栈、动态链接、方法出口
等信息。每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从
入栈到出栈的过程。
本地方法栈( Native Method Stack
本地方法栈用于支持本地方法( native 标识的方法,即非 Java 语言实现的方法)。
虚拟机栈和本地方法栈,当线程请求分配的栈容量超过 JVM 允许的最大容量时抛
StackOverflowError 异常。
线程共享区域
线程共享区域包含:堆和方法区。
堆( Heap
堆是最常处理的区域,它存储在 JVM 启动时创建的数组和对象, JVM 垃圾收集也
主要是在堆上面工作。
如 果 实 际 所 需 的 堆 超 过 了 自 动 内 存 管 理 系 统 能 提 供 的 最 大 容 量 时 抛 出 1
8
OutOfMemoryError 异常。
方法区( Method Area
方法区是可供各条线程共享的运行时内存区域。存储了每一个类的结构信息,例如
运行时常量池( Runtime Constant Pool )、字段和方法数据、构造函数和普通方法
的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法。当创建类
和接口时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大内
存空间后就会抛出 OutOfMemoryError
运行时常量池( Runtime Constant Pool
运行时常量池是方法区的一部分,每一个运行时常量池都分配在 JVM 的方法区中,
在类和接口被加载到 JVM 后,对应的运行时常量池就被创建。运行时常量池是每
一个类或接口的常量池( Constant_Pool )的运行时表现形式,它包括了若干种常量:
编译器可知的数值字面量到必须运行期解析后才能获得的方法或字段的引用。如果
方法区的内存空间不能满足内存分配请求,那 Java 虚 拟 机 将 抛 出 一 个
OutOfMemoryError 异常。栈包含 Frames ,当调用方法时, Frame 被推送到堆栈。
一个 Frame 包含局部变量数组、操作数栈、常量池引用。

什么是乐观锁和悲观锁

   乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐

观锁认为竞争不总是会发生,因此它不需要持有锁,将比较 - 替换这两个动作作为
一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有
相应的重试逻辑。
悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,
悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的
锁,就像 synchronized ,不管三七二十一,直接上了锁就操作资源了。

 实例 

    

乐观锁和悲观锁是并发编程中常用的两种锁机制,用于解决多线程访问共享资源的同步问题。

乐观锁: 乐观锁机制是指在访问共享资源之前,先假设其他线程不会修改该资源,从而避免加锁带来的性能开销。如果发生冲突,则进行冲突检测并处理。

以下是使用乐观锁的示例代码:

import java.util.concurrent.atomic.AtomicInteger;public class OptimisticLockExample {private AtomicInteger count = new AtomicInteger(0);public void increment() {int oldValue;int newValue;do {oldValue = count.get();  // 获取当前值newValue = oldValue + 1;  // 计算新值} while (!count.compareAndSet(oldValue, newValue));  // CAS操作,如果当前值与期望值相等,则更新为新值}public static void main(String[] args) {OptimisticLockExample example = new OptimisticLockExample();Thread thread1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {example.increment();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {example.increment();}});thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Count: " + example.count.get());}
}

悲观锁: 悲观锁机制是指在访问共享资源之前,先假设其他线程会修改该资源,因此加锁保证资源的独占性。只有获得锁的线程能够访问该资源。

以下是使用悲观锁的示例代码:

import java.util.concurrent.locks.ReentrantLock;public class PessimisticLockExample {private int count = 0;private ReentrantLock lock = new ReentrantLock();public void increment() {lock.lock();  // 加锁try {count++;  // 访问共享资源} finally {lock.unlock();  // 解锁}}public static void main(String[] args) {PessimisticLockExample example = new PessimisticLockExample();Thread thread1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {example.increment();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {example.increment();}});thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Count: " + example.count);}
}

以上示例代码分别演示了乐观锁和悲观锁的实现。乐观锁使用CAS操作来保证资源的更新,而悲观锁使用ReentrantLock来实现锁的获取和释放。

总结

    

乐观锁和悲观锁是并发编程中常用的两种锁机制,用于解决多线程环境下的数据一致性问题。

  1. 乐观锁: 乐观锁的核心思想是假设并发情况下不会发生冲突,因此不需要加锁。当多个线程同时访问共享资源时,乐观锁采用一种检测机制来判断是否存在冲突。如果存在冲突,就放弃当前操作,通过重试或回滚来保证数据的一致性。

乐观锁的实现方式主要有两种:

  • 版本号机制:在数据表中增加一个版本号字段,每次更新数据时,同时更新版本号。当多个线程尝试更新同一条数据时,只有其中一个线程能够成功,其余的线程会检测到版本号不一致而放弃操作。
  • CAS(Compare and Swap)操作:通过比较当前值和期望值是否相等,如果相等则更新值,否则重试。

乐观锁适用于读操作较多的场景,能够提高并发性能,但在写操作较多的场景下容易引发冲突。

  1. 悲观锁: 悲观锁的核心思想是假设并发情况下会发生冲突,因此默认加锁保证数据的一致性。当一个线程对共享资源进行读或写操作时,悲观锁会将资源加锁,其他线程需要等待锁释放之后才能访问。

悲观锁的实现方式主要有两种:

  • synchronized关键字:在方法或代码块上加上synchronized关键字,保证同一时间只有一个线程能够访问该代码块或方法。
  • ReentrantLock类:通过ReentrantLock和Condition实现对共享资源的加锁和解锁,能够更灵活地控制锁的获取和释放。

悲观锁适用于写操作较多的场景,能够确保数据的一致性,但会降低并发性能。

相关文章:

  • Java中的JDBC如何连接数据库并执行操作
  • Windows API 速查
  • 每日一题——Java编程练习题
  • Vue3集成Phaser-飞机大战游戏(设计与源码)
  • 基于深度学习的音乐合成算法实例
  • LangChain学习之四种Memory模式使用
  • 基于springboot+vue的医院信息管理系统
  • 计算机毕业设计 | 基于Koa+vue的高校宿舍管理系统宿舍可视化系统
  • Github上一款开源、简洁、强大的任务管理工具:Condution
  • 谨以此文章记录我的蓝桥杯备赛过程
  • Python与Scratch:深入探索两者之间的区别
  • 媳妇面试了一家公司,期望月薪20K,对方没多问就答应了,只要求3天内到岗,可我总觉得哪里不对劲。
  • 【数据库系统概论】函数依赖与范式
  • Jetpack架构组件_4. 数据绑定库页面传递数据
  • ChatGPT成知名度最高生成式AI产品,使用频率却不高
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • 【mysql】环境安装、服务启动、密码设置
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • Apache Zeppelin在Apache Trafodion上的可视化
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • CSS相对定位
  • IP路由与转发
  • JavaScript服务器推送技术之 WebSocket
  • orm2 中文文档 3.1 模型属性
  • PHP的类修饰符与访问修饰符
  • python 装饰器(一)
  • React as a UI Runtime(五、列表)
  • Shadow DOM 内部构造及如何构建独立组件
  • Spring Cloud Feign的两种使用姿势
  • spring cloud gateway 源码解析(4)跨域问题处理
  • Swoft 源码剖析 - 代码自动更新机制
  • 搞机器学习要哪些技能
  • 规范化安全开发 KOA 手脚架
  • 缓存与缓冲
  • 你不可错过的前端面试题(一)
  • 前端性能优化——回流与重绘
  • 如何进阶一名有竞争力的程序员?
  • 深度学习中的信息论知识详解
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 小程序button引导用户授权
  • 小程序滚动组件,左边导航栏与右边内容联动效果实现
  • ​LeetCode解法汇总2696. 删除子串后的字符串最小长度
  • # centos7下FFmpeg环境部署记录
  • # windows 运行框输入mrt提示错误:Windows 找不到文件‘mrt‘。请确定文件名是否正确后,再试一次
  • # 达梦数据库知识点
  • #nginx配置案例
  • #QT(智能家居界面-界面切换)
  • (C语言)球球大作战
  • (leetcode学习)236. 二叉树的最近公共祖先
  • (创新)基于VMD-CNN-BiLSTM的电力负荷预测—代码+数据
  • (切换多语言)vantUI+vue-i18n进行国际化配置及新增没有的语言包
  • (转) 深度模型优化性能 调参
  • .NET Framework 3.5安装教程
  • .NET gRPC 和RESTful简单对比