面试题答案
一键面试原子性
- 概念:原子性是指一个操作是不可中断的,要么全部执行成功,要么全部执行失败。在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作(除了long和double,在32位系统上,它们的读写不是原子性的,但在64位系统上是原子性的)。例如
int a = 10;
,这个操作要么成功将10赋值给a,要么不执行,不存在赋值一半的情况。 - 底层实现:Java中通过
Unsafe
类的一些本地方法来实现原子性操作,例如compareAndSwapInt
等方法,这些方法基于CPU的CAS(Compare - And - Swap)指令,保证了在多线程环境下操作的原子性。
可见性
- 概念:可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。在Java内存模型中,每个线程都有自己的工作内存,线程对变量的操作都在自己的工作内存中进行,而主内存是共享的。当线程修改了共享变量的值后,不会立即刷新到主内存,其他线程从主内存读取变量时,可能获取到的是旧值,从而导致可见性问题。
- 底层实现:通过
volatile
关键字来保证可见性。当一个变量被声明为volatile
时,线程对该变量的写操作会立即刷新到主内存,读操作会直接从主内存读取,从而保证了其他线程能及时看到最新值。另外,synchronized
关键字也能保证可见性,因为在进入synchronized
块时会从主内存读取变量,退出时会将变量刷新到主内存。
联系与区别
- 联系:原子性和可见性都是多线程编程中保证数据一致性的重要特性。在某些场景下,要保证数据的正确操作,既需要原子性,也需要可见性。例如在对共享变量进行复杂的读写操作时,既要保证操作的原子性,又要保证修改后其他线程能立即看到。
- 区别:原子性关注的是操作的完整性,是针对单个操作或指令而言;而可见性关注的是多线程之间变量值的同步,是针对多线程对共享变量的读写而言。
只保证原子性忽略可见性的问题及解决方案
- 问题举例:
public class AtomicityWithoutVisibility {
private static int count = 0;
public static void increment() {
count++;
}
public static void main(String[] args) {
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
increment();
}
});
threads[i].start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Expected: 10000, Actual: " + count);
}
}
在上述代码中,count++
操作本身不是原子性的,它包含读取、加一、写入三个步骤。但即使将count
声明为AtomicInteger
来保证原子性,由于没有保证可见性,不同线程对count
的修改可能不会及时被其他线程看到,导致最终结果小于预期的10000。
2. 解决方案:
- 使用volatile
关键字:将count
声明为volatile
,如下:
public class AtomicityWithVisibility {
private static volatile int count = 0;
public static void increment() {
count++;
}
public static void main(String[] args) {
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
increment();
}
});
threads[i].start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Expected: 10000, Actual: " + count);
}
}
- **使用`AtomicInteger`并结合`volatile`语义**:`AtomicInteger`本身保证了原子性操作,并且其`get`和`set`方法具有`volatile`语义,也能保证可见性。
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
private static AtomicInteger count = new AtomicInteger(0);
public static void increment() {
count.incrementAndGet();
}
public static void main(String[] args) {
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
increment();
}
});
threads[i].start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Expected: 10000, Actual: " + count.get());
}
}