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

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

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

包括但不限于:

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

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

🔎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就可以让编译器不再帮你进行优化

🔎结尾

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

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

相关文章
|
安全 Java
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
1274 1
|
10月前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
10月前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
11月前
|
JavaScript 前端开发 安全
轻松上手Web Worker:多线程解决方案的使用方法与实战指南
轻松上手Web Worker:多线程解决方案的使用方法与实战指南
285 0
|
安全 Java 调度
多线程编程的挑战与解决方案
多线程编程的挑战与解决方案
|
安全 Java
Java多线程编程实践中的常见问题与解决方案
Java多线程编程实践中的常见问题与解决方案
|
缓存 安全 Java
Java中的线程安全问题及解决方案
Java中的线程安全问题及解决方案
|
安全 Java 调度
多线程编程的挑战与解决方案
多线程编程的挑战与解决方案
|
安全 Java 开发者
Java多线程编程实践中的常见问题与解决方案
Java多线程编程实践中的常见问题与解决方案
|
算法 Java
Java多线程基础-13:一文阐明死锁的成因及解决方案
死锁是指多个线程相互等待对方释放资源而造成的一种僵局,导致程序无法正常结束。发生死锁需满足四个条件:互斥、请求与保持、不可抢占和循环等待。避免死锁的方法包括设定加锁顺序、使用银行家算法、设置超时机制、检测与恢复死锁以及减少共享资源。面试中可能会问及死锁的概念、避免策略以及实际经验。
219 1