《volatile使用与学习总结:》多层面分析学习java关键字--volatile

简介: 《volatile使用与学习总结:》多层面分析学习java关键字--volatile

一、简介

volatile是java中提供的一种轻量级的同步机制,相比于synchronized更加轻量,因为volatile避免了线程频繁的上下文切换和调度,但是也有缺点,同步性差并且使用起来易出错。

二、并发编程的三个概念

  1. 原子性
    原子性即一个操作或者多个操作要不全部执行完(执行过程不被干扰),要不都不执行。
  2. 可见性
    可见性指的是,当一个线程操作一个变量的值时,在其他线程是可以及时看到这个变量修改后的值,并能获取来操作。对比与synchronized和lock,这两者也都能保证可见性,因为它们在每次释放锁之前都会将修改好的变量值更新到主内存中,保证了可见性。
  3. 有序性
    即程序执行的顺序是按照代码的先后顺序来执行的。
    java内存模型中所说的有序性总结为,在本线程中看都是有序执行的,在一个线程看另外一个线程都是无序的,也就是说前半句表示“线程内表现为串型语义”,后面句表示“指令重排序”现象和“主内存与工作内存同步延迟”现象。

三、锁的互斥和可见性

  1. 互斥:即一次只允许一个线程持有某个特定的锁,而且一次只允许一个线程访问共享数据
  2. 锁的可见性:在前一个线程释放锁之前对变量进行更改后,将更改后的变量由工作内存更新到主内存中,保证下一个获得该锁的线程对这个变量具有可见性,如果没有及时更新到主内存中后果很严重,会导致数据不一致问题。
    要让volatile线程安全,必须保证两点
    (1)对变量的写操作不依赖于当前值
    (2)该变量没有包含在具有其他变量的变式中
    事实上就是保证该变量操作的原子性。

四、volatile变量的特性

  1. 保证可见性,不保证原子性
    (1)当写一个变量是,当前线程会将该变量从工作内存强制更新到主内存,保证其他线程实时获取的变量是最新的,保证了可见性。
    (2)这个写操作会导致其他线程中这个变量缓存无效。
  2. 禁止指令重排序
    重排序是程序为了更好地性能调度,对指令进行新的排序的一种手段,需要遵守以下规则
    (1)重排序不会对存在数据依赖关系的程序重排序
    (2)重排序是为了优化性能,但无论怎么排序,在单线程下,从排序后的运算结果都不会改变,但是在多线程下重排序则可能会影响结果
    (3)使用volatile能够禁止指令重排序,但要遵守一些规则。
    1)当执行到volatile修饰的变量进行读或者写时,该操作前面的指令都执行完毕,且该操作结果对后面指令可见,而且该操作后面的指令都未执行
    2)当指令优化时,不能将指令放到volatile后面执行,也不能把把volatile后面的语句放到前面执行。

五、volatile不适合的场景

  1. volatile不适合复合操作,下面模拟十个线程对num自增,每个线程自增1000,
    比如 num++,不是一个原子性操作,分为读取,加,赋值三个操作,所以结果达不到10000
public class VolatileTest1 {
    private static volatile int num = 0;
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    num++;
                }
            });
            thread.start();
        }
        System.out.println("num=" + num);
    }
}

结果

num=5657

num=4414

num=7491

…(完全随机)

  1. 解决办法
    (1)使用synchornized锁住变量所在的类
public class VolatileTest1 {
    public static volatile int num = 0;
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        synchronized (VolatileTest1.class) {
                            num++;
                        }
                    }
                }
            }).start();
        }
        // 保证线程执行完毕
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println("num=" + num);
    }
}

结果:

num=100000

(2)使用lock锁住该方法

public class VolatileTest3 {
    public static volatile int num = 0;
    public static Lock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        lock.lock();
                        try {
                            num++;
                        }finally {
                            lock.unlock();
                        }
                    }
                }
            }).start();
        }
        // 保证线程执行完毕
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println("num=" + num);
    }
}

结果:

num=100000

(3)使用并发原子操作类AtomicInteger ,其原理是通过CAS循环的方式来保证原子性

public class VolatileTest5 {
    public static volatile AtomicInteger num = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        num.getAndIncrement();
                    }
                }
            }).start();
        }
        // 保证线程执行完毕
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println("num=" + num);
    }
}

结果:

num=100000

六、单例模式的双重检验锁为什么要加volatile

public class InstanceTest{
  private volatile static InstanceTest instance;
  public static InstanceTest getInstance(){   //1
    if(instance == null){   //2
      synchronized(InstanceTest.class){   //3
        if(instance == null){   //4
          instance = new InstanceTest(); //5
        }
      }
    }
    return instance;   //6
  }
}

在并发情况下,如果没有volatile修饰的话,代码中5处会出现问题instance = new InstanceTest();

这个可以分为三步,

a. memory = allocate() //分配内存
b. ctorInstanc(memory) //初始化对象
c. instance = memory   //设置instance指向刚分配的地址

在编译时,指令重排序,不一定按照a->b->c的顺序来执行,可能会是a->c->b,多线程下一个线程在执行完a,马上执行c,设置instance指向刚分配的地址,这个时候另外一个线程刚好到第2步判断,则会出现不为空,直接跳到第6步,而此时并没有初始化,会返回一个未初始化的对象。

七、volatile原理

volatile可以保证线程的可见性并提供一定的有序性,底层采用的是“内存屏障”来实现的,但是不能保证原子性,需要结合lock,synchornized,AtomicInteger 等来实现线程并发安全。

内存屏障会提供三个功能:

  1. 他不会把volatile修饰的变量执行的操作的前面的指令方放到后面,当然相反也不会将后面的指令放到前面。(禁止指令重排序)
  2. 他会强制将当前修改的变量立即写入主缓存
  3. 如果是写操作,他会将其他线程的缓存置为无效

借鉴学习-侵删

相关文章
|
2月前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
98 43
Java学习十六—掌握注解:让编程更简单
|
21天前
|
监控 算法 Java
jvm-48-java 变更导致压测应用性能下降,如何分析定位原因?
【11月更文挑战第17天】当JVM相关变更导致压测应用性能下降时,可通过检查变更内容(如JVM参数、Java版本、代码变更)、收集性能监控数据(使用JVM监控工具、应用性能监控工具、系统资源监控)、分析垃圾回收情况(GC日志分析、内存泄漏检查)、分析线程和锁(线程状态分析、锁竞争分析)及分析代码执行路径(使用代码性能分析工具、代码审查)等步骤来定位和解决问题。
|
1月前
|
JavaScript 前端开发 Java
java中的this关键字
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。自学前端2年半,正向全栈进发。若我的文章对你有帮助,欢迎关注,持续更新中!🎉🎉🎉
50 9
|
1月前
|
设计模式 JavaScript 前端开发
java中的static关键字
欢迎来到瑞雨溪的博客,博主是一名热爱JavaScript和Vue的大一学生,致力于全栈开发。如果你从我的文章中受益,欢迎关注我,将持续分享更多优质内容。你的支持是我前进的动力!🎉🎉🎉
51 8
|
1月前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
1月前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
46 4
|
1月前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
64 2
|
1月前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
37 2
|
1月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
2月前
|
Java 程序员 编译器
下一篇
DataWorks