作者:xao | 来源:互联网 | 2024-12-10 10:58
在多线程应用开发中,数据竞争、原子性破坏和顺序违背是三种常见的并发缺陷,这些问题通常与对共享资源的不当访问有关。
并发缺陷的分类在国内外研究中有所不同,一般包括但不限于死锁、数据竞争、原子性破坏、顺序违背等。一些研究进一步将并发缺陷细分为七类,除了上述提到的四类外,还包括活锁、饥饿和挂起等问题。
数据竞争 (Data Race)
数据竞争发生在至少两个线程访问同一内存位置,且至少有一个线程进行写操作,而这些访问未受到适当的同步机制保护的情况下。这种情况下,程序的行为可能是不确定的,可能导致错误的结果或系统崩溃。
根据Java语言规范第17章,happens-before关系定义了内存操作(如共享变量的读写)之间的关系。只有当写操作发生在读操作之前时,一个线程的写操作结果才能保证对另一个线程的读操作可见。synchronized关键字、volatile修饰符以及Thread.start()和Thread.join()方法都可以建立happens-before关系。
原子性破坏 (Atomicity Violation)
原子性破坏是指一个线程中的两个代码块(由锁保护的语句序列)的执行与其他线程的一个或多个代码块的执行并发重叠,导致内存内容无法通过任何非重叠顺序的执行来实现。这意味着某些操作序列必须作为一个整体执行,以确保其正确性。
例如,在处理缓冲区时,如果检查缓冲区大小后再决定是否增加容量,但在这两个操作之间发生了上下文切换,可能会导致缓冲区溢出。同样,如果在指针赋值后立即使用该指针,但在赋值和使用之间发生了上下文切换,可能导致空指针异常。
顺序违背 (Order Violation)
顺序违背发生在至少两次内存访问的预期顺序未被遵守时,即程序员期望的操作顺序未被执行。这通常会导致错误或非预期的结果。解决顺序违背的方法通常是使用条件变量或其他同步机制来确保操作的正确顺序。
例如,线程1在初始化io_pending时,S4必须在S2之后执行,否则可能导致初始化不完全的问题。另一个例子是在使用某个变量前未对其进行初始化,这将导致“使用前未初始化”的缺陷。
三者的区别
|
数据竞争 |
顺序违背 |
原子性破坏 |
---|
至少一个线程在等待状态 |
|
√ |
√ |
至少一个线程处于执行状态 |
√ |
√ |
√ |
所有线程都处于执行状态 |
√ |
|
|
产生错误或非预期结果 |
√ |
√ |
√ |
至少一个线程持有锁 |
|
√ |
√ |
不同的线程访问共享内存 |
√ |
√ |
√ |
至少一个内存访问操作是写操作 |
√ |
√ |
√ |
内存访问没有同步机制保护 |
√ |
|
|
在内存访问中,至少有一个正确的执行顺序未被保证 |
|
√ |
|
序列需要被原子执行 |
|
|
√ |