Java核心知识体系 线程安全性讨论

2023-11-29 05:40:10 字數 3174 閱讀 3244

我们都知道,cpu、内存、i/o 设备的速度是有极大差异的,为了合理利用 cpu 的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了优化,主要体现为:

cpu增加了缓存,均衡了与内存之间的速度差异,但会导致可见性问题。

操作系统增加了进程、线程,以分时复用 cpu,进而均衡 cpu 与 i/o 设备的速度差异,但会导致原子性问题。

编译程序优化指令执行次序,使得缓存能够得到更加合理地利用,但会导致有序性问题。

从上面可以看到,虽然多线程平衡了cpu、内存、i/o 设备之间的效率,但是同样也带来了一些问题。

如果有多个线程,对一个共享数据进行操作,但没有采取同步的话,那操作结果可能超出预想,产生不一致。

下面举个粒子,设置一个计数器count,我们通过1000个线程同时对它进行增量操作,看看操作之后的值,是不是符合预想中的1000。

public class unsafethreadtest public int get()
public static void main(string args) throws interruptedexception );countdownlatch.await();关闭线程池 executorsvc.shutdown();system.out.println("最终计数:" threadtest.get())
最终计数:994 //结果跟预期的 1000 不一样
可以看到,上述**输出的结果跟预期的 1000 不一样,我们需要理清楚发生了什么问题?

并发三要素:可见性、原子性、有序性。

可见性问题是指当一个线程修改了一个共享变量的值时,另一个线程可能无法立即看到这个修改。

我们举个简单的例子,看下面这段**:

// 主存中 index 的值默认为 10system.out.println("主存中的值:" index);/thread1 执行赋值index = 100; /thread2 执行的threada = index;
因为thread1修改后的值可能仍然存储在cpu缓存中,而没有被写回主存储器。这种情况下,thread2无法读取到修改后的值,所以导致错误信息。

具体来说,当多个线程同时运行在同一个处理器上时,它们共享该处理器的缓存。如果一个线程修改了某个共享变量的值,该值可能被存储在处理器缓存中,并且未被立即写回到主存储器中。

因此,当另一个线程试图读取该变量的值时,它可能会从主存储器中读取旧的值 10,而不是从处理器缓存中读取已更新的值 100。

原子性:原子性是指一个操作在执行过程中不可分割,即该操作要么完全执行,要么完全不执行。

我们举个简单的例子,看下面这段**:

// 主存中 index 的值默认为 10system.out.println("主存中的值:" index);/thread1 执行增值index +=1; /thread2 执行增值index +=1
以上的信息可以看出:

主存的值为10

i +=1 这个操作实际执行三条 cpu 指令。

变量 i 从内存读取到 cpu寄存器;

在cpu寄存器中执行 i + 1 操作;

将最后的结果i写入内存,因为有缓存机制,所以最终可能写入的是 cpu 缓存而不是内存。

由于cpu分时复用(线程切换)的存在,thread1执行了第一条指令后,就切换到thread2执行,thread2全部执行完成之后,再切换会thread1执行后续两条指令,将造成最后写到内存中的index值是11而不是12。

有序性:即程序执行的顺序按照**的先后顺序执行。

重排序(reordering)是指在计算机系统中,由于处理器优化或编译器优化等原因,导致指令执行的顺序与程序**中的顺序不一致。重排序可能会引起有序性错误,即在并发或多线程环境中,程序执行的顺序与**的先后顺序不一致,导致程序结果不正确或出现意外的结果。

我们举个简单的例子,看下面这段**:

int idx = 10;boolean ischeck = true;idx +=1; /执行语句1 ischeck = false; /执行语句2
上面**定义了一个int型变量,定义了一个boolean类型变量,然后分别对两个变量进行操作。

从**顺序上看,执行语句1是在执行语句2前面的,那么jvm在真正执行这段**的时候会保证语句1一定会在语句2前面执行吗? 不一定,为什么呢? 这里可能会发生指令重排序(instruction reorder)。

重排序(reordering)是指在计算机系统中,由于处理器优化或编译器优化等原因,导致指令执行的顺序与程序**中的顺序不一致。重排序可能会引起有序性错误,即在并发或多线程环境中,程序执行的顺序与**的先后顺序不一致,导致程序结果不正确或出现意外的结果。

重排序引起的有序性错误主要有以下几种情况:

指令重排序:处理器为了优化程序的执行,可能会对指令进行重排序。这种重排序不会改变单线程程序的执行结果,但可能会影响多线程程序的行为。例如,一个线程修改了一个共享变量的值,但由于指令重排序,另一个线程在读取该变量时可能读取到过时的值。

内存访问重排序:处理器为了提高程序的执行效率,可能会对内存访问进行重排序。例如,一个线程先读取一个共享变量的值,然后再写入该值,但由于内存访问重排序,处理器可能会先执行写入操作,再执行读取操作,从而导致其他线程无法正确地读取到修改后的值。

同步操作重排序:在并发或多线程环境中,同步操作可能会被重排序。例如,一个线程先释放了一个锁,然后再执行另一个操作,但由于同步操作重排序,释放锁的操作可能会先于另一个操作执行,从而导致其他线程无法正确地获取锁。

为了避免重排序引起的有序性错误,可以采用一些同步机制来确保程序的执行顺序,如内存屏障(memory barrier,intel 称为 memory fence)、指令fence等。这些同步机制可以确保指令的执行顺序与**中的顺序一致,避免指令重排序和内存访问重排序等问题。同时,也可以使用串行化(serialization)或事务内存(transactional memory)等技术来保证并发程序的有序性。

cpu、内存、i/o 设备的速度是有极大差异的,多线程 的实现是为了合理利用 cpu 的高性能,平衡这三者的速度差异。

多线程情况下,并发产生问题的三要素:可见性、原子性、有序性。

可见性:由cpu缓存引起。

原子性: 由分时复用引起。

有序性: 重排序引起。

作者:翁智华出处:www.cnblogs.com/wzh2010/

网络安全知识体系1 1简介(五)跨学科主题

跨学科主题 许多话题和主题在不同的领域隐式或显式地反复出现kas,并提供一个上下文或跨这些上下文的想法的统一kas。它横穿了为项目选择的结构cybok.在不同的分解中,他们可能是kas自己的权利。这些是知识体系的重要组成部分,因此我们记录了它们这里是最重要的。.安全经济学 信息安全经济学是计算机与社...

安全知识科普教育系统 消防安全模拟训练装置

安全知识科普教育系统。随着社会的发展和人们生活水平的提高,消防安全问题越来越受到人们的关注。为了提高公众的消防安全意识和应急处置能力,消防安全模拟训练装置 模拟灭火体验设备以及安全知识科普教育系统应运而生。这些设备不仅可以提供逼真的模拟体验,还可以帮助人们了解正确的消防安全知识和技能。一 消防安全模...

网络安全知识教育心得

在世纪,网络已经成为人们日常生活的一部分,很多人甚至已经离不开网络。有了网络,人们足不出户便可衣食无忧。前些天刚从电视上看到关于年底网购火爆,快递公司也在 春运 的新闻。以前人们找东西得找人帮忙,现在人们找东西找网帮忙。记得有一次钥匙不知道放到了什么地方,就了一下 钥匙丢在那里了 结果按照网友们提示...