JUC——Chapter02——Concurrent Access To Objects And Variables 对象和变量的并发访问 —— 读《Java多线程编程技术核心技术》笔记 ——下

本文详细探讨了volatile关键字在并发编程中的可见性测试、解决死循环问题、原子性与代码重排序,以及与synchronized的区别。通过实例分析和总结,帮助理解volatile在多线程中的应用和限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

📖 对象及变量的并发访问

所需掌握的知识点

  • synchronized 对象监视器为 Object 时的使用方法
  • synchronized 对象监视器为 Class 时的使用方法
  • 非线程安全问题如何出现
  • 关键字 volatile 的主要作用
  • 关键字 volatile 与 synchronized 的区别及使用情况

📑 2.3 volatile 关键字

volatile 使用上具有以下3个特性

  • 可见性 —— B 线程能够马上看到 A 线程 更改的数据
  • 原子性 —— 体现在赋值原子性
  • 禁止代码重排序

🔖 2.3.1 可见性测试

⭐️ 单线程出现死循环

代码示例

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/29 19:06
 * @description:
 * @modified By: Alascanfu
 **/
public class PrintStringTest {
    static class PrintString{
        private boolean isContinuePrint = true;
        
        public boolean isContinuePrint(){
            return isContinuePrint;
        }
        
        public void setContinuePrint(boolean isContinuePrint){
            this.isContinuePrint = isContinuePrint;
        }
        
        public void printStringMethod(){
            try {
                while (isContinuePrint == true ){
                    System.out.println("run printStringMethod threadName = "
                    + Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        PrintString printString = new PrintString();
        printString.printStringMethod();
        System.out.println("我要停止它 ! stopThread = " +
            Thread.currentThread().getName());
        printString.setContinuePrint(false);
    }
}

运行结果

在这里插入图片描述

  • 停不下来的原因主要就是 main 线程一直在处理 while() 循环,导致程序不能继续执行后面的代码,解决的办法当然是用多线程技术
⭐️ 使用多线程解决死循环

修改代码

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/29 19:06
 * @description:
 * @modified By: Alascanfu
 **/
public class PrintStringTest {
    static class PrintString implements Runnable{
        private boolean isContinuePrint = true;
        
        public boolean isContinuePrint(){
            return isContinuePrint;
        }
        
        public void setContinuePrint(boolean isContinuePrint){
            this.isContinuePrint = isContinuePrint;
        }
        
        public void printStringMethod(){
            try {
                while (isContinuePrint == true ){
                    System.out.println("run printStringMethod threadName = "
                    + Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void run() {
            printStringMethod();
        }
    }
    
    public static void main(String[] args) {
        PrintString printString = new PrintString();
        Thread thread = new Thread(printString);
        thread.start();
        System.out.println("我要停止它 ! stopThread = " +
            Thread.currentThread().getName());
        printString.setContinuePrint(false);
    }
}

运行结果

在这里插入图片描述

⭐️ 使用多线程有可能出现死循环

示例代码

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/29 19:39
 * @description:
 * @modified By: Alascanfu
 **/
public class RunThread extends Thread{
    private boolean isRunning = true ;
    public boolean isRunning(){
        return isRunning;
    }
    
    public void setRunning(boolean isRunning){
        this.isRunning = isRunning;
    }
    @Override
    public void run() {
        super.run();
        System.out.println("进入 run 了");
        while (isRunning == true ){
        
        }
        System.out.println("线程 被停止了 !");
    }
    
    public static void main(String[] args) {
        try {
            RunThread thread = new RunThread();
            thread.start();
            ;
            Thread.sleep(1000);
            thread.setRunning(false);
            System.out.println("已经赋值为 false");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果

在这里插入图片描述

  • 程序运行出现了死循环进程且未进行销毁

  • 线程被停止永远不会被执行

❓ 是什么原因导致的死循环呢?

  • 在启动线程时,因为变量 private boolean isRunning = true ; 分别存储在公共内存及线程的私有内存当中,线程运行后在 线程的私有内存中取得 isRunning 的值一直是 true。

  • 而代码 “thread.setRunning(false);” 虽然被执行,却是将公共内存中的 isRunning 变量改成 false。

  • 这个问题就是私有内存中的值和公共内存中的值是不同步造成的,可以通过 volatile 关键字来解决volatile 的主要作用就是当线程访问 isRunning 变量时,强制地从公共内存中取值。

⭐️ 使用 volatile 关键字解决多线程出现的死循环

示例代码

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/29 19:39
 * @description:
 * @modified By: Alascanfu
 **/
public class RunThread extends Thread{
    private volatile boolean isRunning = true ;
    public boolean isRunning(){
        return isRunning;
    }
    
    public void setRunning(boolean isRunning){
        this.isRunning = isRunning;
    }
    @Override
    public void run() {
        super.run();
        System.out.println("进入 run 了");
        while (isRunning == true ){
        
        }
        System.out.println("线程 被停止了 !");
    }
    
    public static void main(String[] args) {
        try {
            RunThread thread = new RunThread();
            thread.start();
            ;
            Thread.sleep(1000);
            thread.setRunning(false);
            System.out.println("已经赋值为 false");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果

在这里插入图片描述

线程具有私有内存

在这里插入图片描述

读取公共内存

在这里插入图片描述

⭐️ synchronized 代码块也具有增加可见性作用

synchronized 关键字可以使多个线程访问同一个资源,具有同步性,也可以使线程私有内存中的变量与公共内存中的变量同步。——可见性

示例代码

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/29 19:39
 * @description:
 * @modified By: Alascanfu
 **/
public class RunThread extends Thread{
    private boolean isRunning = true ;
    public boolean isRunning(){
        return isRunning;
    }
    
    public void setRunning(boolean isRunning){
        this.isRunning = isRunning;
    }
    @Override
    public void run() {
        super.run();
        System.out.println("进入 run 了");
        while (isRunning == true ){
            synchronized (this){
            
            }
        }
        System.out.println("线程 被停止了 !");
    }
    
    public static void main(String[] args) {
        try {
            RunThread thread = new RunThread();
            thread.start();
            Thread.sleep(1000);
            thread.setRunning(false);
            System.out.println("已经赋值为 false");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果

在这里插入图片描述

📚 小结

synchronized 关键字会把私有内存中的数据同公共内存同步,使私有内存中的数据和公共内存中的数据一致。

🔖 2.3.2 原子性与非原子性的测试

在 32 位 JDK 与 64 位 JDK版本中,long 与 double 64位的数据类型是否实现赋值写原子性有着不同。

📚 volatile 关键字最知名的缺点是 不支持运算原子性,也就是多个线程对用 volatile 修饰的变量 i 执行 i--i++ 操作还是会被分解成三步,造成非线程安全问题。

⭐️ volatile 操作是非原子性的

volatile 关键字不支持 i ++ 运算原子性,使用多线程执行 volatile int i ++ 赋值操作是非原子性的 , i – 操作的行为也是一样的。

示例代码

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/29 22:25
 * @description:
 * @modified By: Alascanfu
 **/
public class MyThread extends Thread {
    public static volatile int count;
    
    private static void addCount() {
        for (int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println("count = " + count);
    }
    
    @Override
    public void run() {
        super.run();
        addCount();
    }
    
    public static void main(String[] args) {
        MyThread[] myThreads = new MyThread[100];
        for (int i = 0; i < 100; i++) {
            myThreads[i] = new MyThread();
        }
        
        for (int i = 0; i < 100; i++) {
            myThreads[i].start();
        }
    }
}

运行结果

在这里插入图片描述

📚 小结:

  • volatile 关键字主要是在多个线程中可以感知实例变量被更改了,并且可以获得最新的值时使用,增加了可见性时使用。
  • volatile 关键字提示线程每次从公共内存中去读取变量,而不是从私有内存中去读取,这样就保证了同步数据的可见性。

值得注意的是:如果修改实例变量中的数据,比如 i++ 则这样的操作其实并不是一个原子操作,也就是非线程安全的

📚 总结:volatile 保证数据在线程之间的可见性,但不保证同步性。

⭐️ 使用 Atomic 原子类进行 i++ 操作实现原子性

原子操作是不能分割的整体,没有其他线程能够中断或检查正在原子操作中的变量。一个原子(atomic) 类型就是一个原子操作可用的类型,它可以在没有锁的情况下做到线程安全(thread-safe)

示例代码

import java.util.concurrent.atomic.AtomicInteger;

/***
 * @author: Alascanfu
 * @date : Created in 2022/6/29 22:52
 * @description: Atomic Integer Test
 * @modified By: Alascanfu
 **/
public class AddCountThread extends Thread{
    private AtomicInteger count = new AtomicInteger(0);
    
    @Override
    public void run() {
        for (int i = 0 ; i < 10000 ; i++){
            System.out.println(count.incrementAndGet());
        }
    }
    
    public static void main(String[] args) {
        AddCountThread countThreadService = new AddCountThread();
        Thread t1 = new Thread(countThreadService);
        t1.start();
        Thread t2 = new Thread(countThreadService);
        t2.start();
        Thread t3 = new Thread(countThreadService);
        t3.start();
        Thread t4 = new Thread(countThreadService);
        t4.start();
        Thread t5 = new Thread(countThreadService);
        t5.start();
    }
}

运行结果

在这里插入图片描述

⭐️ 逻辑混乱与解决方案

及时在有逻辑性的情况下,原子类的输出结果也具有随机性。

📚 方法和方法之间的调用是非原子的,此时可以通过同步解决该问题。

🔖 2.3.3 禁止代码重排序的测试

什么是重排序?

在 Java 程序运行时,JIT(Just-In-Time Compiler , 即时编译器)为了优化程序的运行,可以动态地改变程序代码运行的顺序。

A 代码 - 重耗时
B 代码 - 轻耗时
C 代码 - 重耗时
D 代码 - 轻耗时

在多线程的环境当中,JIT 有可能进行代码重排,重排后的代码顺序可能如下:

B 代码 - 轻耗时
D 代码 - 轻耗时
A 代码 - 重耗时
C 代码 - 重耗时
  • 这样做的主要原因是在 CPU 流水线中 这 4 个指令 同时执行的 , 轻耗时的代码 在很大程度上会先执行完,以让出 CPU 流水线资源 供其他指令使用, 所以代码重排是为了追求更高的程序运行效率。
  • 重排序发生在没有依赖关系时
  • volatile 关键字可以禁止 代码重排序 。

比如代码如下

A 变量的操作
B 变量的操作
volatile Z 变量的操作
C 变量的操作
D 变量的操作

1️⃣ AB 可以重排序

2️⃣ CD 可以重排序

3️⃣ AB 不可以重排到 Z 的后面

4️⃣ CD 不可以重排到 Z 的前面

📚简单来说:变量 Z 是一道屏障,是一堵 Z 变量之前或之后的代码不可以跨域 Z 变量。synchronized 关键字也具有同样的特性。

⭐️ volatile 关键字之前的代码可以重排

⭐️ volatile 关键字之后的代码可以重排

⭐️ volatile 关键字之前的代码不可以重排到volatile之后

⭐️ volatile 关键字之后的代码不可以重排到volatile之前

⭐️ synchronized 关键字之前的代码不可以重排到 synchronized 之后

⭐️ synchronized 关键字之后的代码不可以重排到 synchronized 之前

🔖 总结

synchronized 关键字的主要作用:保证同一时刻,只有一个线程可以执行某一个方法或者某一个代码块。 synchronized 可以修饰方法以及代码块,随着 JDK 的版本升级 , synchronized 在执行效率上得到了很大的提升。它包括了可见性、原子性和禁止代码重排序。

volatile关键字的主要作用是让其他线程可以看到最新的值,volatile 只可以修饰变量。它也包括三个特征:可见性、操作非原子性、禁止代码重排序。

⭐️ synchronized 与 volatile 的使用场景

1️⃣ 当想实现一个变量的值被更改,而其他线程能取到最新的值时,就要对变量使用 volatile 。

2️⃣ 如果多个线程对同一个对象中的同一个实例变量进行写操作,为了避免出现非线程安全问题,就要使用 synchronized。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alascanfu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值