JVM学习笔记——内存模型篇

JVM学习笔记——内存模型篇在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的内存模型部分
我们会分为以下几部分进行介绍:

  • 内存模型
  • 乐观锁与悲观锁
  • synchronized优化
内存模型这一小节我们来详细介绍一下内存模型和内存模型的三个特性
内存模型简介首先我们来简单介绍一下内存模型:
  • 内存模型,全称Java Memory Model,也就是我们常说的JMM
  • JMM中定义了一套在多线程读写共享数据时 , 对数据的可见性,有序性和原子性的规则和保障
内存模型之原子性我们将在下面仔细介绍原子性的特点
原子性介绍我们首先介绍一下原子性:
  • 原子性是指将一系列操作规划为一个操作,全称不可分离进行
原子性的注意点:
  • 我们在单线程下不会出现原子性的问题
  • 但在多线程下,每条语句的实际底层操作不止一步 , 可能就会导致操作错误
原子性问题我们给出一个简单的例子来解释原子性:
package cn.itcast.jvm.t4.avo;// 在下述操作中,我们分别创造两个线程,分别执行i++和i--50000次 , 按正常逻辑来说结果应该为0public class Demo4_1 {static int i = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int j = 0; j < 50000; j++) {i++;}});Thread t2 = new Thread(() -> {for (int j = 0; j < 50000; j++) {i--;}});t1.start();t2.start();t1.join();t2.join();System.out.println(i);}}但我们多次运行的结果如下:
// 每次结果均不相同302-9860原子性分析首先我们分别给出i++和i--的底层操作:
// i++getstatic i// 获取静态变量i的值iconst_1// 准备常量1iadd// 加法putstatic i// 将修改后的值存入静态变量i// i--getstatic i// 获取静态变量i的值iconst_1// 准备常量1isub// 减法putstatic i// 将修改后的值存入静态变量i我们的原子性分为两种情况:
  • 单线程情况下:我们的顺序肯定是按照正常顺序来执行
  • 多线程情况下:我们i++的操作按顺序执行 , i--的操作按顺序执行,但两者操作可能会交替进行
首先我们给出单线程情况下底层代码:
// 单线程// 假设i的初始值为0getstatic i// 线程1-获取静态变量i的值 线程内i=0iconst_1// 线程1-准备常量1iadd// 线程1-自增 线程内i=1putstatic i// 线程1-将修改后的值存入静态变量i 静态变量i=1getstatic i// 线程1-获取静态变量i的值 线程内i=1iconst_1// 线程1-准备常量1isub// 线程1-自减 线程内i=0putstatic i// 线程1-将修改后的值存入静态变量i 静态变量i=0然后我们分别给出多线程情况下多种结果的底层代码:
// 多线程// 负数// 假设i的初始值为0getstatic i// 线程1-获取静态变量i的值 线程内i=0getstatic i// 线程2-获取静态变量i的值 线程内i=0iconst_1// 线程1-准备常量1iadd// 线程1-自增 线程内i=1putstatic i// 线程1-将修改后的值存入静态变量i 静态变量i=1iconst_1// 线程2-准备常量1isub// 线程2-自减 线程内i=-1putstatic i// 线程2-将修改后的值存入静态变量i 静态变量i=-1// 正数// 假设i的初始值为0getstatic i// 线程1-获取静态变量i的值 线程内i=0getstatic i// 线程2-获取静态变量i的值 线程内i=0iconst_1// 线程1-准备常量1iadd// 线程1-自增 线程内i=1iconst_1// 线程2-准备常量1isub// 线程2-自减 线程内i=-1putstatic i// 线程2-将修改后的值存入静态变量i 静态变量i=-1putstatic i// 线程1-将修改后的值存入静态变量i 静态变量i=1原子性实现那么我们该如何实现多线程的原子性:
  • 使用synchronized(同步关键字)
我们这里给出synchronized的使用方式:
synchronized( 对象 ) { // 要作为原子操作代码}我们如果要实现之前的代码,我们可以将代码修改为:
package cn.itcast.jvm.t4.avo;public class Demo4_1 {// 这里的i应该被多线程共用,设为静态变量static int i = 0;// 这里是Obj对象,我们设置它为锁,注意两个线程中的synchronized所对应的锁应该是同一个对象(锁)static Object obj = new Object();public static void main(String[] args) throws InterruptedException {// 采用synchronized设置锁实现原子性,这样i++操作就会完整进行Thread t1 = new Thread(() -> {synchronized (obj) {for (int j = 0; j < 50000; j++) {i++;}}});Thread t2 = new Thread(() -> {// 采用synchronized设置锁实现原子性,这样i--操作就会完整进行synchronized (obj) {for (int j = 0; j < 50000; j++) {i--;}}});t1.start();t2.start();t1.join();t2.join();// 我们的输出结果自然是0了~System.out.println(i);}}

推荐阅读