[Java]volatile关键字

简介: [Java]volatile关键字

在学习此关键字之前,我们先了解一下JMM规范和并发编程中的三个概念。

1、JMM规范

JMM(Java module memory,Java内存模型)是一个抽象概念,并不真实存在于内存。它是用于定义程序中各个变量(成员变量、类变量、数组元素等)的一组规范和规则,指定变量的访问方式。

规定: \color{red}{规定:}规定:

  1. 线程解锁之前必须将共享变量刷新回主内存;
  2. 线程加锁之前必须读取主内存中变量的最新值到工作空间;
  3. 解锁和加锁必须是同一把锁。

大家可能不解其意,这就需要涉及另一个概念:线程空间 \color{green}{线程空间}线程空间.。

什么是线程空间? \color{grey}{什么是线程空间?}什么是线程空间?

程序执行JMM规范的实体是线程,当线程创建时,JMM会为其创建一个私有内存(也称为工作内存、本地内存或栈空间 工作内存、本地内存或栈空间工作内存、本地内存或栈空间)。JMM规定所有变量都保存在主内存,线程访问变量时需为变量创建一个副本至工作内存进行操作,完成后将变量值返回主内存,且线程通信在主内存进行。

2、并发编程的三个概念

  1. 可见性: \color{green}{可见性:}可见性:指线程对变量的修改,其他线程可见;
  2. 原子性: \color{blue}{原子性:}原子性:指线程对变量的操作的整个过程不会被阻塞或分割;
  3. 有序性: \color{brown}{有序性:}有序性:也称为“指令重排” \color{red}{“指令重排”}指令重排,指程序运行时,编译器基于提高性能需要,以指令间的数据依赖性作为依据对指令进行重新排列。执行顺序:编译器重排 → 指令并行重排 → 内存系统重排。

3、volatile

3.1 概述

volatile是一种轻量级的同步机制,而synchronized属于重量级(“级”是指对变量访问的限制程度)。

volatile遵循JMM规范实现了可见性和有序性,但不保证原子性。因此,限制线程在访问由volatile修饰的变量时,从主内存获取数据,而不是从工作内存,在数据操作完成后再刷新回主内存,故在保证原子性的情况下,可实现线程安全。

如何保证原子性?如程序中不存在多线程对变量进行非原子性操作,举个例:a++是原子操作,而a+=1不是。

3.2 volatile 的一个经典应用

关于单例模式,可查阅博文《关于对Java单例模式的理解与简述》。

从文中可知,“双重检测机制” \color{green}{“双重检测机制”}双重检测机制可解决“懒汉式”的线程安全问题。其实,“双重同步锁”也有漏洞。

以那篇博文的示例为例:

instance = new Singleton();

实例化分为三步:

  1. 创建实例,分配内存;
  2. 实例初始化;
  3. instance指向此实例。

其中,2和3都依赖于1,而2与3之间没有依赖关系,故指令重排会将2与3对调(原因可能是实例初始化耗时较长)。

因此,当instance指向实例时,实例可能还未初始化,下一个线程就会出现并发问题(暂不清楚原因),用volatile禁止指令重排即可解决。

4、volatile 的运用

从上文可知,volatile关键字可以解决这两种情形下的线程安全问题。

  1. 多线程并发访问变量,线程体中不存在非原子操作的情况;
  2. 弥补双重同步锁 \color{green}{双重同步锁}双重同步锁的漏洞。

我们一一进行测试。(学以致用)

4.1 情形一

创建10个线程对同一个成员变量并发修改一万次。

volatile int a;
public static void main(String[] args) throws Exception {
    C c1 = new C();// C 是当前类名
    int i = 10;
    while (i-- > 0) {
        new Thread(() -> {
            int n = 10000;
            while (n-- > 0) {
                c1.a++;
            }
        }).start();
    }
    Thread.sleep(10000);// 主线程停留10s足以保证10个子线程运行完成
    System.out.println(c1.a);
}

最后c1.a的输出结果并不是100000(10s足够10个子线程执行完成)。可见,并未解决线程安全问题。

4.2 情形二

多线程并发调用newInstance()获取单例模式类实例。

实体类。

class SingleTon {
    private static SingleTon instance;
    private SingleTon() {}
    public static SingleTon newInstance() {
        if (instance == null) {
            synchronized (SingleTon.class) {
                if (instance == null) {
                    instance = new SingleTon();
                }
            }
        }
        return instance;
    }
}

测试:创建一万个线程并发调用newInstance(),判断获取的实例是否都为单例。

List<SingleTon> list = new Vector<>();
int i = 10000;
while (i-- > 0) {
    new Thread(() -> {
        SingleTon s1 = SingleTon.getInstance();
        if (list.size() > 0 && list.indexOf(s1) == -1)
            System.out.println("违反单例");// 未执行
        list.add(s1);
    }).start();
}
Thread.sleep(1000);
System.out.println(list.size());// 10000

Vector 类线程同步,故list.add(s1)也是线程同步的。

未打印“违反单例”,表示list中存储的所有s1都指向同一个实例,保证了“单例”,说明线程安全。

不过,还证明不了这是volatile的功劳,因为双重检测机制 \color{blue}{双重检测机制}双重检测机制本身对线程安全就有很大的保证性。

于是,我把10000改成了100000,好吧。。。还是未打印“违反单例”。

最后

大家肯定也看出来了,在上面的示例中,我的本意是想创建十万个线程调用newInstance(),通过是否打印“违反单例”来触发双重同步锁 \color{brown}{双重同步锁}双重同步锁的漏洞,然后用volatile声明instance来解决线程安全问题,可失败了。。。

因此,我对volatile关键字的理解还不够透彻。

本文的目的是为了让大家对volatile关键字有一个初步的了解,我继续努力!

本文完结。

相关文章
|
22天前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
【4月更文挑战第6天】Java中的`synchronized`关键字用于处理多线程并发,确保共享资源的线程安全。它可以修饰方法或代码块,实现互斥访问。当用于方法时,锁定对象实例或类对象;用于代码块时,锁定指定对象。过度使用可能导致性能问题,应注意避免锁持有时间过长、死锁,并考虑使用`java.util.concurrent`包中的高级工具。正确理解和使用`synchronized`是编写线程安全程序的关键。
|
2天前
|
存储 安全 Java
聊聊Java关键字synchronized(下)
聊聊Java关键字synchronized(下)
7 0
|
2天前
|
监控 安全 Java
聊聊Java关键字synchronized(上)
聊聊Java关键字synchronized
7 0
|
4天前
|
安全 Java 编译器
是时候来唠一唠synchronized关键字了,Java多线程的必问考点!
本文简要介绍了Java中的`synchronized`关键字,它是用于保证多线程环境下的同步,解决原子性、可见性和顺序性问题。从JDK1.6开始,synchronized进行了优化,性能得到提升,现在仍可在项目中使用。synchronized有三种用法:修饰实例方法、静态方法和代码块。文章还讨论了synchronized修饰代码块的锁对象、静态与非静态方法调用的互斥性,以及构造方法不能被同步修饰。此外,通过反汇编展示了`synchronized`在方法和代码块上的底层实现,涉及ObjectMonitor和monitorenter/monitorexit指令。
18 0
|
4天前
|
Java
两千字讲明白java中instanceof关键字的使用!
两千字讲明白java中instanceof关键字的使用!
12 0
|
5天前
|
Java 开发者
Java基础知识整理,注释、关键字、运算符
在日常的工作中,总会遇到很多大段的代码,逻辑复杂,看得人云山雾绕,这时候若能言简意赅的加上注释,会让阅读者豁然开朗,这就是注释的魅力!
37 11
|
9天前
|
安全 Java 开发者
Java并发编程:深入理解Synchronized关键字
【4月更文挑战第19天】 在Java多线程编程中,为了确保数据的一致性和线程安全,我们经常需要使用到同步机制。其中,`synchronized`关键字是最为常见的一种方式,它能够保证在同一时刻只有一个线程可以访问某个对象的特定代码段。本文将深入探讨`synchronized`关键字的原理、用法以及性能影响,并通过具体示例来展示如何在Java程序中有效地应用这一技术。
|
12天前
|
存储 缓存 安全
Java并发基础之互斥同步、非阻塞同步、指令重排与volatile
在Java中,多线程编程常常涉及到共享数据的访问,这时候就需要考虑线程安全问题。Java提供了多种机制来实现线程安全,其中包括互斥同步(Mutex Synchronization)、非阻塞同步(Non-blocking Synchronization)、以及volatile关键字等。 互斥同步(Mutex Synchronization) 互斥同步是一种基本的同步手段,它要求在任何时刻,只有一个线程可以执行某个方法或某个代码块,其他线程必须等待。Java中的synchronized关键字就是实现互斥同步的常用手段。当一个线程进入一个synchronized方法或代码块时,它需要先获得锁,如果
24 0
|
13天前
|
Java
Java关键字(1)
Java关键字(1)