Java——多线程高并发系列之volatile关键字

简介: Java——多线程高并发系列之volatile关键字

文章目录:


写在前面:synchronizedvolatile关键字的作用、区别?

Demo1(不使用volatile,不保证可见性)

Demo2(使用volatile,保证可见性)

Demo3volatile不保证原子性)

写在前面:synchronized和volatile关键字的作用、区别?


一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

保证了不同线程对这个变量进行操作时的可见性,不保证原子性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

禁止进行指令重排序。

·       volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

·       volatile仅能使用在变量级别;synchronized则可以使用在变量、方法级别的。

·       volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可见性和原子性。

·       volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

·       volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。


Demo1(不使用volatile,不保证可见性)


我们说volatile只能用来修饰变量,那么这个例子中,没有使用volatile修饰静态内部类中的continuePrint变量,它的初始值为true,在main线程中,开启子线程之后,它会去执行printStringMethod方法,然后打印打印一句话,之后是一个循环whilecontinuePrint {} ,对于这个子线程来说,它的这个while循环是一直成立的,所以它会一直在这执行这个空内容的while循环。而main睡眠1秒之后,它将continuePrint的值修改为了false,那么这个时候子线程能否知道、并且终止while循环呢?答案是不能,因为continuePrint这个局部变量是子线程独占的,我们都知道局部变量存储在栈空间中,而每个线程的栈空间都是独立不共享的,它们共享的仅仅是堆区和方法区。所以即使你的main主线程修改了continuePrint变量为false,但是在子线程的眼中,continuePrint变量仍然为true。所以执行结果中就会一直卡在这里了。

package com.szh.volatiletest;
/**
 * volatile保证可见性
 */
public class Test01 {
    public static void main(String[] args) {
        PrintString ps=new PrintString();
        //开启子线程,让子线程执行 ps 对象中的 printStringMethod() 方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                ps.printStringMethod();
            }
        }).start();
        //main线程睡眠1000ms
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("在main线程中修改打印标志");
        /*
            main线程在这里对 continuePrint 做了修改,但是子线程中是读取不到的
            解决办法:使用 volatile 关键字修饰 continuePrint
                    volatile 关键字的作用可以强制线程从公共内存中读取数据,而不是从工作内存中读取
         */
        ps.setContinuePrint(false);
    }
    static class PrintString {
        private boolean continuePrint=true;
        public PrintString setContinuePrint(boolean continuePrint) {
            this.continuePrint=continuePrint;
            return this;
        }
        public void printStringMethod() {
            System.out.println(Thread.currentThread().getName() + "开始......");
            while (continuePrint) {
            }
            System.out.println(Thread.currentThread().getName() + "结束......");
        }
    }
}


那么,如何才能使main线程修改完continuePrint变量的值之后,在子线程中也可以读取到呢?答案就是使用 volatile 关键字。

Demo2(使用volatile,保证可见性)


首先说一下,volatile 关键字的作用就是使变量在多个线程之间是可见的!!!

在上个例子的基础上,我们将continuePrint变量的修饰符中添加上 volatile,那么这个变量对于main主线程、Thread-0子线程都是可见的了。也就是说,当Thread-0子线程执行到whilecontinuePrint{} 时,先执行一会这个空的循环体,然后main主线程将continuePrint变量修改为了false,这个时候,Thread-0子线程就可以读取到continuePrint变量被修改为了false,那么while循环就不成立了,自然而然的就结束了。

package com.szh.volatiletest;
/**
 * volatile保证可见性
 */
public class Test01 {
    public static void main(String[] args) {
        PrintString ps=new PrintString();
        //开启子线程,让子线程执行 ps 对象中的 printStringMethod() 方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                ps.printStringMethod();
            }
        }).start();
        //main线程睡眠1000ms
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("在main线程中修改打印标志");
        /*
            main线程在这里对 continuePrint 做了修改,但是子线程中是读取不到的
            解决办法:使用 volatile 关键字修饰 continuePrint
                    volatile 关键字的作用可以强制线程从公共内存中读取数据,而不是从工作内存中读取
         */
        ps.setContinuePrint(false);
    }
    static class PrintString {
        private volatile boolean continuePrint=true;
        public PrintString setContinuePrint(boolean continuePrint) {
            this.continuePrint=continuePrint;
            return this;
        }
        public void printStringMethod() {
            System.out.println(Thread.currentThread().getName() + "开始......");
            while (continuePrint) {
            }
            System.out.println(Thread.currentThread().getName() + "结束......");
        }
    }
}

Demo3(volatile不保证原子性)


这个例子演示volatile不保证原子性。

main主线程中创建10个子线程,这10个子线程分别去执行1000count++操作,那么根据简单的逻辑推理,这10个子线程的执行结果应该是类似于:1000400080002000......这样的,都是10的整数倍,但是从输出结果中看到,并非如此。

这里的原因可能有:其中一个子线程的for循环还未执行完,另外的子线程就抢走了你的CPU执行权,另外的子线程开始执行它的for循环了,而此时的count变量因为有volatile修饰,所以count对其他子线程是可见的。

                              其中一个子线程的count++操作执行到一半时,被另外的子线程抢走了CPU的执行权。(所以说这里的count++并不是原子性操作)

那么,Java中有两种方式实现原子性: 一种是使用锁;  另一种利用处理器的 CAS(Compare and Swap)指令。

锁具有排它性,保证共享变量在某一时刻只能被一个线程访问。CAS 指令直接在硬件(处理器和内存)层次上实现,看作是硬件锁。

package com.szh.volatiletest;
/**
 * volatile不保证原子性
 */
public class Test02 {
    public static void main(String[] args) {
        //在main线程中创建10个子线程
        for (int i = 0; i < 10; i++) {
            new MyThread().start();
        }
    }
    static class MyThread extends Thread {
        public volatile static int count;
        public static void addCount() {
            for (int i = 0; i < 1000; i++) {
                count++;
            }
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
        @Override
        public void run() {
            addCount();
        }
    }
}

相关文章
|
10月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
401 0
|
12月前
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
345 0
|
8月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
11月前
|
缓存 监控 Cloud Native
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化
本文深入解析了Java Solon v3.2.0框架的实战应用,聚焦高并发与低内存消耗场景。通过响应式编程、云原生支持、内存优化等特性,结合API网关、数据库操作及分布式缓存实例,展示其在秒杀系统中的性能优势。文章还提供了Docker部署、监控方案及实际效果数据,助力开发者构建高效稳定的应用系统。代码示例详尽,适合希望提升系统性能的Java开发者参考。
544 4
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化
|
11月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
730 5
|
11月前
|
监控 搜索推荐 Java
Java 多线程最新实操技术与应用场景全解析:从基础到进阶
本文深入探讨了Java多线程的现代并发编程技术,涵盖Java 8+新特性,如CompletableFuture异步处理、Stream并行流操作,以及Reactive编程中的Reactor框架。通过具体代码示例,讲解了异步任务组合、并行流优化及响应式编程的核心概念(Flux与Mono)。同时对比了同步、CompletableFuture和Reactor三种实现方式的性能,并总结了最佳实践,帮助开发者构建高效、扩展性强的应用。资源地址:[点击下载](https://pan.quark.cn/s/14fcf913bae6)。
545 3
|
10月前
|
缓存 NoSQL Java
Java 项目实操高并发电商系统核心模块实现从基础到进阶的长尾技术要点详解 Java 项目实操
本项目实战实现高并发电商系统核心模块,涵盖商品、订单与库存服务。采用Spring Boot 3、Redis 7、RabbitMQ等最新技术栈,通过秒杀场景解决库存超卖、限流熔断及分布式事务难题。结合多级缓存优化查询性能,提升系统稳定性与吞吐能力,适用于Java微服务开发进阶学习。
469 0
|
12月前
|
算法 Java 调度
Java多线程基础
本文主要讲解多线程相关知识,分为两部分。第一部分涵盖多线程概念(并发与并行、进程与线程)、Java程序运行原理(JVM启动多线程特性)、实现多线程的两种方式(继承Thread类与实现Runnable接口)及其区别。第二部分涉及线程同步(同步锁的应用场景与代码示例)及线程间通信(wait()与notify()方法的使用)。通过多个Demo代码实例,深入浅出地解析多线程的核心知识点,帮助读者掌握其实现与应用技巧。
187 1
|
12月前
|
Java
java 多线程异常处理
本文介绍了Java中ThreadGroup的异常处理机制,重点讲解UncaughtExceptionHandler的使用。通过示例代码展示了当线程的run()方法抛出未捕获异常时,JVM如何依次查找并调用线程的异常处理器、线程组的uncaughtException方法或默认异常处理器。文章还提供了具体代码和输出结果,帮助理解不同处理器的优先级与执行逻辑。
243 1