1. 数据竞争导致数据不一致
两个线程分别把一个变量增加10000次,理论上变量最后的值是20000,实际上小于20000
package org.example;
public class synchronizedTest {
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
// 线程 1:对 counter 进行 10000 次自增操作
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter++;
}
});
// 线程 2:对 counter 进行 10000 次自增操作
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter++;
}
});
// 启动两个线程
thread1.start();
thread2.start();
// 等待两个线程执行完毕
thread1.join();
thread2.join();
// 预期结果应该是 20000,但由于数据竞争,实际结果可能小于 20000
System.out.println("Counter value: " + counter);
}
}
counter++
的底层操作
counter++
这个操作在 Java 代码里看似是一个简单的自增操作,但在计算机底层,它实际上是由多个步骤组成的:
- 读取(Read):从内存中读取
counter
的当前值到 CPU 的寄存器中。 - 加 1(Increment):在寄存器中对读取的值进行加 1 操作。
- 写入(Write):将寄存器中加 1 后的值写回到内存中的
counter
变量。
执行过程
- 线程 1 读取
counter
的值:线程 1 从内存中读取counter
的值 0 到它的寄存器中。此时线程 1 的寄存器中的值为 0,内存中counter
的值仍为 0。 - 线程调度切换到线程 2:操作系统将 CPU 控制权交给线程 2。
- 线程 2 读取
counter
的值:线程 2 从内存中读取counter
的值 0 到它的寄存器中。此时线程 2 的寄存器中的值为 0,内存中counter
的值还是 0。 - 线程 2 完成自增并写入:线程 2 在寄存器中对值进行加 1 操作,寄存器中的值变为 1,然后将 1 写回到内存中。此时内存中
counter
的值变为 1。 - 线程调度切换回线程 1:操作系统将 CPU 控制权交回给线程 1。
- 线程 1 完成自增并写入:线程 1 在它的寄存器中对之前读取的 0 进行加 1 操作,寄存器中的值变为 1,然后将 1 写回到内存中。此时内存中
counter
的值还是 1。
在上述过程中,线程 1 和线程 2 都进行了一次自增操作,但由于数据竞争,内存中 counter
的值只增加了 1,而不是预期的 2,这就导致了数据不一致。
修改后,把修改数据的值操作封装为方法用synchronized修饰,或者用synchronized修饰代码块,确保同一时间只有一个线程能够访问方法
package org.example;
public class synchronizedTest {
private static int counter = 0;
private static final Object staticLock = new Object();
public static synchronized void increment() {
counter++;
}
public static void increment() {
// 对当前对象(this)加锁
synchronized (staticLock) {
counter++;
}
}
public static void main(String[] args) throws InterruptedException {
// 线程 1:对 counter 进行 10000 次自增操作
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
increment();
}
});
// 线程 2:对 counter 进行 10000 次自增操作
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
increment();
}
});
// 启动两个线程
thread1.start();
thread2.start();
// 等待两个线程执行完毕
thread1.join();
thread2.join();
// 预期结果应该是 20000,但由于数据竞争,实际结果可能小于 20000
System.out.println("Counter value: " + counter);
}
}
2,并发修改
线程1在迭代List的同时,线程2添加新元素
package org.example;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class synchronizedTest {
public static void main(String[] args) {
// 创建一个 ArrayList
List<Integer> list = new ArrayList<>();
for (int i &