线程安全问题及解决方案(下)

简介: 线程安全问题及解决方案(下)

影响线程安全问题的因素有很多

包括但不限于:

  • 内存可见性
  • 指令重排序

本篇将通过实例对上述原因进行讲解

🔎1.示例

🌻示例代码

import java.util.Scanner;
public class Test {
    public static int n = 0;
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
           while(n == 0) {
               //空着
           }
            System.out.println("线程结束运行");
        },"这是t1线程");
        Thread t2 = new Thread(() -> {
            Scanner scan = new Scanner(System.in);
            System.out.println("请输入一个不等于0的数字终止线程:");
            n = scan.nextInt();
        });
        t1.start();
        t2.start();
    }
}

代码描述:

  • 上述代码启动了 t1,t2两个线程
  • t1线程的while()循环体内部是空着的
  • 通过t2线程修改n的值让while()的循环条件不满足,从而让线程t1结束运行

那么这样做能否成功呢?

答案是不能

运行结果

通过运行结果我们看到 t1线程仍然处于运行状态


🌻原因分析

注意这里的while(n==0)

此处需要执行2个步骤

  • 1从内存读取数据n到寄存器
  • 2比较寄存器中的值是否等于0

小知识

访问速度:寄存器>内存>硬盘

由于寄存器的访问速度大于内存,且循环体内部是空着的.

每次从内存读取n的值到寄存器,读取的结果是相同的(由于循环体是空着的,所以几乎不占用时间执行循环体里的操作,所以在较短的时间内读取了多次n的值,发现n的值还是0).

读取操作(内存)相对于比较操作(寄存器)是一个比较大的时间开销,编译器就默认帮我们进行了优化

这也就解释了为什么 t2线程修改n的值t1线程没能停下来

上述现象可以解释为内存可见性的缘故

所谓内存可见性,就是多线程环境下,编译器对代码进行了优化,产生了误判,从而引起了bug


那么如果循环体里不是空着的, t1线程是不是就会停下来了呢?

答案是对的

运行结果

当循环体非空时,读取操作就不再被看作是一个比较大的时间开销,编译器也就不再帮忙进行优化了


🌻解决方案

当循环体为空时,可以加入volatile避免编译器进行优化

直接访问工作内存(寄存器), 速度非常快, 但是可能出现数据不一致的情况.

加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了

也就是说,volatile 能保证内存可见性

完整代码

public class Test2 {
    volatile public static int n = 0;
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
           while(n == 0) {
               //空着
               /*try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println("循环体非空");*/
           }
            System.out.println("线程结束运行");
        },"这是t1线程");
        Thread t2 = new Thread(() -> {
            Scanner scan = new Scanner(System.in);
            System.out.println("请输入一个不等于0的数字终止线程:");
            n = scan.nextInt();
        });
        t1.start();
        t2.start();
    }
}

运行结果

🔎2.示例

volatile还有另外一个作用,禁止指令重排序

那么什么是指令重排序呢

举个栗子

有一天

你的女朋友让你去超时帮她分别买(1)薯片(2)旺仔牛奶(3)QQ糖(4)曲奇饼干

这时你选择的路线是入口–>(1)薯片–>(4)曲奇饼干–>(3)QQ糖–>(2)旺仔牛奶–>出口

编译器就会帮你进行优化(指令重排序)(执行顺序入口–>(1)薯片–>(2)旺仔牛奶–>(4)曲奇饼干->(3)QQ糖–>出口)

此时如果加入volatile就可以让编译器不再帮你进行优化

🔎结尾

创作不易,如果对您有帮助,希望您能点个免费的赞👍

大家有什么不太理解的,可以私信或者评论区留言,一起加油

相关文章
|
5月前
|
安全 Java
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
930 1
|
1月前
|
JavaScript 前端开发 安全
轻松上手Web Worker:多线程解决方案的使用方法与实战指南
轻松上手Web Worker:多线程解决方案的使用方法与实战指南
41 0
|
5月前
|
安全 Java 调度
多线程编程的挑战与解决方案
多线程编程的挑战与解决方案
|
4月前
|
缓存 安全 Java
Java中的线程安全问题及解决方案
Java中的线程安全问题及解决方案
|
5月前
|
安全 Java
Java多线程编程实践中的常见问题与解决方案
Java多线程编程实践中的常见问题与解决方案
|
4月前
|
安全 Java 调度
多线程编程的挑战与解决方案
多线程编程的挑战与解决方案
|
4月前
|
安全 Java 开发者
Java多线程编程实践中的常见问题与解决方案
Java多线程编程实践中的常见问题与解决方案
|
4月前
|
设计模式 安全 Java
Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
110 0
|
4月前
|
缓存 安全 Java
Java中的线程安全问题及解决方案
Java中的线程安全问题及解决方案
|
4月前
|
安全 Java 开发者
Java并发编程中的线程安全问题及解决方案探讨
在Java编程中,特别是在并发编程领域,线程安全问题是开发过程中常见且关键的挑战。本文将深入探讨Java中的线程安全性,分析常见的线程安全问题,并介绍相应的解决方案,帮助开发者更好地理解和应对并发环境下的挑战。【7月更文挑战第3天】
94 0