https://juejin.cn/post/6844903890224152584?searchId=20240228142139E6AC18D1C1498D59FFE5
线程安全是程式设计中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
《Java Concurrency In Practice》的作者 Brian Goetz 对线程安全是这样理解的,当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行问题,也不需要进行额外的同步,而调用这个对象的行为都可以获得正确的结果,那这个对象便是线程安全的。
Java 的线程内存模型是基于 Java Memory Model (JMM) ,定义了在多线程环境下,变量如何被各个线程共享和传递。JMM 是为了解决并发编程中的可见性、原子性、有序性等问题而设计的。
Java Memory Model (JMM) 的主要特点:
主内存:存放所有变量的值,是所有线程共享的内存区域。
工作内存:每个线程都有一个私有的工作内存,它保存了该线程使用的变量的副本。线程对变量的所有操作(读、写)都在自己的工作内存(CPU寄存器)中进行,不能直接访问主内存中的变量,线程之间是不可见的
上图描述了一个多线程执行场景。
线程 A 和线程 B 分别对主内存的变量进行读写操作。其中主内存中的变量为共享变量,也就是说此变量只此一份,多个线程间共享。但是线程不能直接读写主内存的共享变量,每个线程都有自己的工作内存,线程需要读写主内存的共享变量时需要先将该变量拷贝一份副本到自己的工作内存,然后在自己的工作内存中对该变量进行所有操作,线程工作内存对变量副本完成操作之后需要将结果同步至主内存。
一组操作(一行或多行代码)是不可拆分的最小执行单位, 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题
一个线程对共享变量的修改,对其他线程来说是不可见的,除非通过某种同步机制(如 volatile 关键字、synchronized 关键字等)来确保可见性。
JMM 允许编译器和处理器对指令进行重排序,但重排序必须遵守 happens-before 规则,以保证程序的顺序性。
如果操作 A happens-before 操作 B,那么 A 的效果对 B 可见,并且 A 的执行顺序在 B 之前。
规则包括:程序顺序规则、监视器锁规则、volatile 变量规则、传递性规则、线程启动规则、线程终止规则、线程中断规则和 final 字段规则等。
要考虑线程安全问题,就需要先考虑Java并发的三大基本特性:原子性、可见性以及有序性
详细见上文,常见线程安全问题有:
1.原子性问题
当多个线程同时访问和修改同一个共享变量时,如果操作不是原子性的,就可能导致数据不一致。例如,自增、自减、赋值等操作在多线程环境下可能不是原子性的,需要使用同步机制来确保操作的原子性。
2.可见性问题
个线程对共享变量的修改对其他线程是不可见的,除非通过特定的同步机制来确保可见性。这可能导致多个线程操作共享变量时,无法看到其他线程所做的修改,从而导致数据不一致或程序行为异常。
3.有序性问题
由于JVM和处理器对指令的重排序,可能会导致多线程程序的执行顺序与预期不符。即使代码逻辑上看似正确,重排序也可能导致实际执行结果与预期不符,从而引发线程安全问题。
4.活跃性问题
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。