🔒 第4章测试:线程安全问题

测试你对线程安全问题的理解程度

1. 以下哪个操作在Java中是原子的?

  • A. i++
  • B. i = 5
  • C. i += 5
  • D. if (i == 0) i = 1

解释:

在Java中,对基本数据类型(除long和double外)的简单赋值操作是原子的。i = 5是一个简单的赋值操作,是原子的。而i++、i += 5都是复合操作,包含读取、计算、写入多个步骤,不是原子的。条件赋值也是复合操作。

2. 竞态条件(Race Condition)的根本原因是什么?

  • A. 线程执行速度不同
  • B. 多个线程同时访问共享资源且缺乏适当同步
  • C. 线程优先级设置不当
  • D. 内存不足

解释:

竞态条件的根本原因是多个线程同时访问和修改共享资源,而没有适当的同步机制来协调这些访问。这导致程序的正确性依赖于线程执行的时序,产生不确定的结果。

3. volatile关键字主要解决什么问题?

  • A. 原子性问题
  • B. 可见性和有序性问题
  • C. 线程安全问题
  • D. 性能问题

解释:

volatile关键字主要解决可见性和有序性问题。它确保变量的读写直接在主内存中进行,保证了变量修改对所有线程的可见性,并且禁止指令重排序。但volatile不能解决原子性问题。

4. 以下哪种情况可能导致可见性问题?

  • A. CPU缓存
  • B. 编译器优化
  • C. JVM内存模型
  • D. 以上都是

解释:

可见性问题可能由多种因素导致:CPU缓存使得变量修改只存在于某个CPU的缓存中;编译器优化可能将变量缓存在寄存器中;JVM内存模型允许线程在本地内存中缓存共享变量的副本。这些都可能导致一个线程的修改对其他线程不可见。

5. 在双重检查锁定模式中,为什么instance变量需要声明为volatile?

  • A. 防止指令重排序
  • B. 保证原子性
  • C. 提高性能
  • D. 减少内存使用

解释:

在双重检查锁定模式中,instance变量需要声明为volatile主要是为了防止指令重排序。对象创建包括分配内存、初始化对象、赋值给引用三个步骤,如果发生重排序,其他线程可能看到未完全初始化的对象。volatile确保这些操作的有序性。

6. 以下哪种方式可以实现线程安全?

  • A. 使用不可变对象
  • B. 线程封闭(ThreadLocal)
  • C. 同步机制(synchronized)
  • D. 以上都可以

解释:

实现线程安全有多种方式:不可变对象一旦创建就不能修改,天然线程安全;ThreadLocal为每个线程提供独立的变量副本,避免共享;synchronized提供互斥访问,确保同一时间只有一个线程访问共享资源。这些都是有效的线程安全实现方式。

7. happens-before规则中,以下哪个说法是正确的?

  • A. 只适用于synchronized关键字
  • B. volatile写操作happens-before后续的volatile读操作
  • C. 只在单线程中有效
  • D. 不具有传递性

解释:

happens-before规则中,volatile写操作happens-before后续的volatile读操作是正确的。这个规则确保了volatile变量的可见性和有序性。happens-before规则适用于多种同步机制,不仅仅是synchronized,而且具有传递性。

8. 以下代码中可能出现什么问题?

if (instance == null) {
    instance = new Singleton();
}
  • A. 内存泄漏
  • B. 竞态条件
  • C. 死锁
  • D. 栈溢出

解释:

这段代码存在典型的竞态条件问题。在多线程环境下,多个线程可能同时执行到if判断,都发现instance为null,然后都去创建新的实例,违反了单例模式的设计初衷。这是"检查后执行"模式的经典问题。