Java中多线程与并发_volatile关键字的深入理解

 更新时间:2020年12月14日 10:47:38   作者:shuPush  
这篇文章主要给大家介绍了关于Java中多线程与并发_volatile关键字的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、volatile关键字

volatile是JVM提供的一种轻量级的同步机制,特性:

1.保证内存可见性

2.不保证原子性

3.防止指令重排序

二、JMM(Java Memory Model)

Java内存模型中规定了所有的变量都存储在主内存中(如虚拟机物理内存中的一部分),每条线程还有自己的工作内存(如CPU中的高速缓存),线程的工作内存中保存了该线程使用到的变量到主内存的副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成,线程、主内存和工作内存的交互关系如下图所示:

三、验证

1.验证volatile的可见性

1.1 假如 int num = 0; num变量之前根本没有添加volatile关键字修饰,没有可见性

1.2 添加了volatile,可以解决可见性问题

MyData类

class MyData {
 volatile int num = 0;

 public void addT060() {
 this.num = 60;
 }
}

内存可见性验证,其中两个线程分别为AAA线程和main线程

 //volatile可以保证可见性,及时通知其它线程,主内存的值已经被修改
 @Test
 public void seeOkByVolatile() {
 MyData myData = new MyData();//资源类

 new Thread(() -> {
  System.out.println(Thread.currentThread().getName() + "\t come in");
  //暂停一会线程
  try{
  TimeUnit.SECONDS.sleep(3);
  }catch (InterruptedException e) {
  e.printStackTrace();
  }
  myData.addT060();
  System.out.println(Thread.currentThread().getName() + "\t update num value: " + myData.num);
 },"AAA").start();

 //第2个线程是我们的main线程
 while (myData.num == 0) {
  //main线程就一直在这里等待循环,直到num值不再等于0.
 }
 System.out.println(Thread.currentThread().getName() + "\t mission is over,main get num value: " + myData.num );
 }

对num变量加volatile修饰后结果

AAA come in
AAA update num value: 60
main 我能见到AAA线程对num修改的结果啦,main get num value: 60

Process finished with exit code 0

2.验证volatile不保证原子性

2.1 原子性指的是什么意思?

不可分割,完整性,也即某个线程正在做某个具体任务时,中间不可以被加塞或者被分割。需要整体完整。要么同时成功,要么同时失败。

2.2 volatile不保证原子性的案例演示

2.3 为什么不保证原子性?

2.4 如何保证原子性

加sync

使用我们juc下的AtomicInteger (底层实现CAS)

给MyData类加addPlusPlus()方法

class MyData {//MyData.java ===> MyData.class ===> JVM字节码
 int num = 0;

 public void addT060() {
 this.num = 60;
 }

 //请注意,此时num前面是加了关键字修饰的,volatile不保证原子性
 public void addPlusPlus() {
 num++;
 }
}

2.2 volatile不保证原子性的案例演示

num++在多线程操作的情况下不保证原子性的

创建20个线程并行执行num++操作2000次,多次测试,结果不为40000

public static void main(String[] args) {
 MyData myData = new MyData();

 for (int i = 1; i <= 20; i++ ) {

  new Thread(() -> {
  for (int j = 1; j <= 2000; j++) {
   myData.addPlusPlus();
  }

  },String.valueOf(i)).start();
 }

 //需要等待上面20个线程都全部计算完成后,再用main线程取得最终的结果值看是多少?
 while(Thread.activeCount() > 2) {
  Thread.yield();
 }

 System.out.println(Thread.currentThread().getName() + "\t finally num value:" + myData.num);
 }

结果:数值小于40000,出现写值丢失的情况

main  finally num value:38480

Process finished with exit code 0

2.3 为什么不保证原子性?

因为当线程A对num++操作从自己的工作内存刷新到主内存时,还未通知到其他线程主内存变量有更新的瞬间,其他线程对num变量的操作结果也对主内存进行了刷新,从而导致了写值丢失的情况

num++通过汇编指令分析,通过javap反编译得到如下汇编指令

class com.slx.juc.MyData {
 volatile int num;

 com.slx.juc.MyData();
 Code:
 0: aload_0
 1: invokespecial #1   // Method java/lang/Object."<init>":()V
 4: aload_0
 5: iconst_0
 6: putfield #2   // Field num:I
 9: return

 public void addT060();
 Code:
 0: aload_0
 1: bipush 60
 3: putfield #2   // Field num:I
 6: return

 public void addPlusPlus();
 Code:
 0: aload_0
 1: dup
 2: getfield #2   // Field num:I
 5: iconst_1
 6: iadd
 7: putfield #2   // Field num:I
 10: return
}

可见num++被拆分成了3个步骤,简称:读-改-写

  • 执行getfield拿到原始num;
  • 执行iadd进行加1操作;
  • 执行putfield写把累加后的值写回

2.4 如何保证原子性

加sync

使用我们juc下的AtomicInteger (底层实现CAS)

MyData类中添加原子类操作方法

 AtomicInteger atomicInteger = new AtomicInteger();
 public void addMyAtomic() {
 atomicInteger.getAndIncrement();
 }

调用该方法打印结果

 public static void main(String[] args) {
 MyData myData = new MyData();

 for (int i = 1; i <= 20; i++ ) {

  new Thread(() -> {
  for (int j = 1; j <= 2000; j++) {
   myData.addMyAtomic();
  }

  },String.valueOf(i)).start();
 }

 //需要等待上面20个线程都全部计算完成后,再用main线程取得最终的结果值看是多少?
 while(Thread.activeCount() > 2) {
  Thread.yield();
 }

 System.out.println(Thread.currentThread().getName() + "\t AtomicInteger type ,finally num value:" + myData.atomicInteger);
 }

测试结果为40000,不会出现之前int类型的丢失值的情况

main  AtomicInteger type ,finally num value:40000

Process finished with exit code 0

总结

到此这篇关于Java中多线程与并发_volatile关键字的文章就介绍到这了,更多相关Java多线程与并发_volatile关键字内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java并发编程学习之ThreadLocal源码详析

    Java并发编程学习之ThreadLocal源码详析

    这篇文章主要给大家介绍了关于Java并发编程学习之源码分析ThreadLocal的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-06-06
  • SpringBoot整合Flyway的方法(数据库版本迁移工具)

    SpringBoot整合Flyway的方法(数据库版本迁移工具)

    这篇文章主要介绍了SpringBoot整合Flyway的方法(数据库版本迁移工具),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • Java编程删除链表中重复的节点问题解决思路及源码分享

    Java编程删除链表中重复的节点问题解决思路及源码分享

    这篇文章主要介绍了Java编程删除链表中重复的节点问题解决思路及源码分享,具有一定参考价值,这里分享给大家,供需要的朋友了解。
    2017-10-10
  • 总结Java常用加解密方法AES SHA1 md5

    总结Java常用加解密方法AES SHA1 md5

    这篇文章主要为大家介绍了Java常用加密方法AES SHA1 md5总结及示例demo,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06
  • Springmvc数据回显实现原理实例解析

    Springmvc数据回显实现原理实例解析

    这篇文章主要介绍了Springmvc数据回显实现原理实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • java控制台输入示例分享

    java控制台输入示例分享

    这篇文章主要介绍了java控制台输入示例分享,需要的朋友可以参考下
    2014-03-03
  • Java的方法和this关键字如何理解与应用

    Java的方法和this关键字如何理解与应用

    Java语言中的“方法”(Method)在其他语言当中也可能被称为“函数”(Function)。对于一些复杂的代码逻辑,如果希望重复使用这些代码,并且做到“随时任意使用”,那么就可以将这些代码放在一个大括号{}当中,并且起一个名字。使用代码的时候,直接找到名字调用即可
    2021-10-10
  • SpringMVC 处理后端日期格式的示例详解

    SpringMVC 处理后端日期格式的示例详解

    在WebMvcConfiguration中扩展SpringMVC的消息转换器,统一对日期类型进行格式处理,本文给大家介绍SpringMVC处理后端日期格式,感兴趣的朋友一起看看吧
    2023-11-11
  • 解决因jdk版本引起的TypeNotPresentExceptionProxy异常

    解决因jdk版本引起的TypeNotPresentExceptionProxy异常

    这篇文章介绍了解决因jdk版本引起的TypeNotPresentExceptionProxy异常的方法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-12-12
  • Java抽象定义以及举例代码

    Java抽象定义以及举例代码

    这篇文章主要给大家介绍了关于Java抽象定义以及举例的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04

最新评论