热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java多线程与并发_volatile关键字详解

Java多线程与并发_volatile关键字详解仅仅活着是不够的,还需要有阳光、自由,和一点花的芬芳一、volatile关键字volatile是JVM提供的一种轻量级的同步机制,特
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 &#061; 1; i <&#061; 20; i&#043;&#043; ) {
new Thread(() -> {
for (int j &#061; 1; j <&#061; 2000; j&#043;&#043;) {
myData.addPlusPlus();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程都全部计算完成后&#xff0c;再用main线程取得最终的结果值看是多少&#xff1f;
while(Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() &#043; "\t finally num value:" &#043; myData.num);
}

结果&#xff1a;数值小于40000&#xff0c;出现写值丢失的情况

main finally num value:38480
Process finished with exit code 0

2.3 为什么不保证原子性&#xff1f;

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

num&#043;&#043;通过汇编指令分析&#xff0c;通过javap反编译得到如下汇编指令

class com.slx.juc.MyData {
volatile int num;
com.slx.juc.MyData();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()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&#043;&#043;被拆分成了3个步骤,简称&#xff1a;读-改-写



  1. 执行getfield拿到原始num&#xff1b;

  2. 执行iadd进行加1操作&#xff1b;

  3. 执行putfield写把累加后的值写回


2.4 如何保证原子性

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

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

AtomicInteger atomicInteger &#061; new AtomicInteger();
public void addMyAtomic() {
atomicInteger.getAndIncrement();
}

调用该方法打印结果

public static void main(String[] args) {
MyData myData &#061; new MyData();
for (int i &#061; 1; i <&#061; 20; i&#043;&#043; ) {
new Thread(() -> {
for (int j &#061; 1; j <&#061; 2000; j&#043;&#043;) {
myData.addMyAtomic();
}
},String.valueOf(i)).start();
}
//需要等待上面20个线程都全部计算完成后&#xff0c;再用main线程取得最终的结果值看是多少&#xff1f;
while(Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() &#043; "\t AtomicInteger type ,finally num value:" &#043; myData.atomicInteger);
}

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

main AtomicInteger type ,finally num value:40000
Process finished with exit code 0

本文地址:https://blog.csdn.net/for_my_life/article/details/89260698



推荐阅读
  • 深入解析CAS机制:全面替代传统锁的底层原理与应用
    本文深入探讨了CAS(Compare-and-Swap)机制,分析了其作为传统锁的替代方案在并发控制中的优势与原理。CAS通过原子操作确保数据的一致性,避免了传统锁带来的性能瓶颈和死锁问题。文章详细解析了CAS的工作机制,并结合实际应用场景,展示了其在高并发环境下的高效性和可靠性。 ... [详细]
  • 在多线程并发环境中,普通变量的操作往往是线程不安全的。本文通过一个简单的例子,展示了如何使用 AtomicInteger 类及其核心的 CAS 无锁算法来保证线程安全。 ... [详细]
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • 线程能否先以安全方式获取对象,再进行非安全发布? ... [详细]
  • 在 Linux 环境下,多线程编程是实现高效并发处理的重要技术。本文通过具体的实战案例,详细分析了多线程编程的关键技术和常见问题。文章首先介绍了多线程的基本概念和创建方法,然后通过实例代码展示了如何使用 pthreads 库进行线程同步和通信。此外,还探讨了多线程程序中的性能优化技巧和调试方法,为开发者提供了宝贵的实践经验。 ... [详细]
  • Java中不同类型的常量池(字符串常量池、Class常量池和运行时常量池)的对比与关联分析
    在研究Java虚拟机的过程中,笔者发现存在多种类型的常量池,包括字符串常量池、Class常量池和运行时常量池。通过查阅CSDN、博客园等相关资料,对这些常量池的特性、用途及其相互关系进行了详细探讨。本文将深入分析这三种常量池的差异与联系,帮助读者更好地理解Java虚拟机的内部机制。 ... [详细]
  • Python全局解释器锁(GIL)机制详解
    在Python中,线程是操作系统级别的原生线程。为了确保多线程环境下的内存安全,Python虚拟机引入了全局解释器锁(Global Interpreter Lock,简称GIL)。GIL是一种互斥锁,用于保护对解释器状态的访问,防止多个线程同时执行字节码。尽管GIL有助于简化内存管理,但它也限制了多核处理器上多线程程序的并行性能。本文将深入探讨GIL的工作原理及其对Python多线程编程的影响。 ... [详细]
  • Ihavetwomethodsofgeneratingmdistinctrandomnumbersintherange[0..n-1]我有两种方法在范围[0.n-1]中生 ... [详细]
  • 本文是Java并发编程系列的开篇之作,将详细解析Java 1.5及以上版本中提供的并发工具。文章假设读者已经具备同步和易失性关键字的基本知识,重点介绍信号量机制的内部工作原理及其在实际开发中的应用。 ... [详细]
  • 本文探讨了如何利用Java代码获取当前本地操作系统中正在运行的进程列表及其详细信息。通过引入必要的包和类,开发者可以轻松地实现这一功能,为系统监控和管理提供有力支持。示例代码展示了具体实现方法,适用于需要了解系统进程状态的开发人员。 ... [详细]
  • 经过两天的努力,终于成功解决了半平面交模板题POJ3335的问题。原来是在`OnLeft`函数中漏掉了关键的等于号。通过这次训练,不仅加深了对半平面交算法的理解,还提升了调试和代码实现的能力。未来将继续深入研究计算几何的其他核心问题,进一步巩固和拓展相关知识。 ... [详细]
author-avatar
兔宝宝牛宝宝_198
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有