多线程并发情况下的数据一致性问题

简介: 多线程并发情况下的数据一致性问题

首先,有一个静态的布尔变量:

public static boolean  flag = false;


然后,开启两个线程。

A线程:

new Thread(() -> {
        System.out.println(Thread.currentThread() + "start...");
while(!flag) {
}
        System.out.println(Thread.currentThread() + "success...");
}).start() ;


很明显,这是一个死循环。

B线程:

//延迟1秒,确保上面的先执行Thread.sleep(1000);
new Thread(() -> {
        flag = true;
        System.out.println(Thread.currentThread() +  "flag 变为 true了!");
}).start() ;


B线程在1秒后执行,把flag变为true。那么按理说,因为flag是静态属性,它的值发生变化后,A线程应该感应到这种变化,从而跳出死循环。


可是实际上:


Thread[Thread-0,5,main]start...

Thread[Thread-1,5,main]flag 变为 true了!


A线程一直在死循环,并没有发生变化。


为啥会和想的不同?


2c4ab1c10f8a4feb8e68ffc65ba742e9.png


如图,JMM内存模型中,每一个线程都有一个高速缓冲区,这个区域是每个线程自己独有的内存空间,flag其实在主内存中,每个线程是拷贝了一个flag副本到自己的内存。


因为A线程有死循环,一直在占用着flag副本,也就没有发现该变量已经被其他线程修改了。


解决办法--用volatile关键字


public static volatile boolean  flag = false;


当flag被volatile关键字修饰,它的值发生变化时,JVM会立刻将flag重新写入主内存。并且通知其他线程(引用了这个副本的线程)立刻销毁flag变量。


从而迫使这些线程重新从主内存读取flag,这样就可以做到保证多线程下变量的数据一致性了。


实际应用--双重检测锁机制下的对象半实例化问题解决

public class MySingleton {
    private static volatile MySingleton mySingleton;
    private MySingleton() {}
    public static MySingleton newInstance() {
        if(mySingleton == null) {
            synchronized (mySingleton) {
                if(mySingleton == null) {
                    mySingleton = new MySingleton();
                }
            }
        }
        return  mySingleton;
    }
}


为什么会出现半实例化?


mySingleton = new MySingleton();


这行代码在汇编里面执行的情况下大致是这样的,伪代码如下:


1.mySingleton 分配内存


2.new mySingleton() 实例化


3.mySingleton实例对象赋值给mySingleton


其中,步骤2 和 步骤3 即便颠倒顺序也不会影响最终的结果,所以CPU可能会进行指令重排序。如果步骤3先执行了,而恰好在这个时候别的线程检测到mySingleton不是null,就直接返回了。此时,mySingleton甚至还没有调用构造方法呢,只是一个空的引用(但已经不是null了)。


所以,这就导致了对象半实例化。解决方法也是加上volatile关键字。


相关文章
|
3天前
|
Java
Java Socket编程与多线程:提升客户端-服务器通信的并发性能
【6月更文挑战第21天】Java网络编程中,Socket结合多线程提升并发性能,服务器对每个客户端连接启动新线程处理,如示例所示,实现每个客户端的独立操作。多线程利用多核处理器能力,避免串行等待,提升响应速度。防止死锁需减少共享资源,统一锁定顺序,使用超时和重试策略。使用synchronized、ReentrantLock等维持数据一致性。多线程带来性能提升的同时,也伴随复杂性和挑战。
|
12天前
|
安全 Java API
Java并发基础-启动和终止线程
Java并发基础-启动和终止线程
21 0
|
12天前
|
Java 调度
Java并发基础-线程简介(状态、常用方法)
Java并发基础-线程简介(状态、常用方法)
17 0
|
5天前
|
监控 程序员 调度
协程实现单线程并发(入门)
协程实现单线程并发(入门)
12 1
|
29天前
|
分布式计算 Java Hadoop
NameNode 处理线程配置(心跳并发)
NameNode线程池处理客户端和数据节点请求,如读写文件及心跳、块报告。通过调整`dfs.namenode.handler.count`(默认10,示例设为21)在`hdfs-site.xml`中可控制并发处理能力。线程数过多或过少都可能影响性能,需平衡资源使用并进行基准测试以确定最佳值。合理线程数可通过公式`int(math.log(N) * 20)`计算,N为服务器数量。例如,3台服务器的计算结果为21。
|
30天前
|
安全 Java 开发者
探索Java中的多线程编程与并发控制
多线程编程是Java编程中不可或缺的一部分,它允许程序同时执行多个任务,从而显著提高程序的整体性能。然而,多线程编程也带来了诸如数据不一致、死锁等并发问题。本文将深入探讨Java中的多线程编程技术,包括线程的创建、同步与通信,并介绍几种常用的并发控制策略,旨在帮助读者更好地理解并发编程的复杂性和挑战,并学会如何编写高效、安全的并发程序。
|
1月前
|
安全 Go 对象存储
C++多线程编程:并发与同步的实战应用
本文介绍了C++中的多线程编程,包括基础知识和实战应用。C++借助`<thread>`库支持多线程,通过`std::thread`创建线程执行任务。文章探讨了并发与同步的概念,如互斥锁(Mutex)用于保护共享资源,条件变量(Condition Variable)协调线程等待与通知,以及原子操作(Atomic Operations)保证线程安全。实战部分展示了如何使用多线程进行并发计算,利用`std::async`实现异步任务并获取结果。多线程编程能提高效率,但也需注意数据竞争和同步问题,以确保程序的正确性。
|
1月前
|
Java 测试技术 Python
Python的多线程允许在同一进程中并发执行任务
【5月更文挑战第17天】Python的多线程允许在同一进程中并发执行任务。示例1展示了创建5个线程打印"Hello World",每个线程调用同一函数并使用`join()`等待所有线程完成。示例2使用`ThreadPoolExecutor`下载网页,创建线程池处理多个URL,打印出每个网页的大小。Python多线程还可用于线程间通信和同步,如使用Queue和Lock。
45 1
|
19天前
|
安全 Java 大数据
Java性能优化(七)-多线程调优-并发容器的使用
Java性能优化(七)-多线程调优-并发容器的使用
22 0
|
20天前
|
算法 Java Linux
线程的魔法:揭开现代操作系统并发执行的面纱
线程的魔法:揭开现代操作系统并发执行的面纱
17 0