Java 中之所以有这么多锁,根本原因就是多线程。
当多个线程同时访问共享数据时,会产生三个经典问题——原子性、可见性、有序性。锁就是为了解决这些问题而诞生的工具。
下面用生活中的例子 + 代码来分别说明为什么需要锁,以及不同的锁解决什么问题。
假设你和其他9个朋友们共同有一个银行账户,余额 100 元。
你们俩同时去 ATM 取钱,每人取 10 元。正常逻辑:取完后余额应为 0 元。
但由于多线程并发,可能出现下面情况:
package com.thinkdifferent.aipicturebackend;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class NoLockDemo {
static int balance = 100;
public static void withdraw(int amount) {
if (balance >= amount) {
// 模拟取钱过程中的耗时操作
try { Thread.sleep(10); } catch (InterruptedException e) {}
balance = balance - amount;
}
}
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // corePoolSize
10, // maximumPoolSize
60L, // keepAliveTime
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(10), // 有界队列,避免内存爆炸
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
for (int i = 1; i <= 10; i++) {
int taskid=i;
executor.execute(() -> {
withdraw(10);
System.out.println(Thread.currentThread().getName() + " 进行取钱,余额为: "+balance+"任务id: "+taskid);
});
}
executor.shutdown();
Thread.sleep(1000);
System.out.println("最终余额:" + balance);
}
}结果:最终余额可能不是 0。
原因:线程同时读到 balance=100,都判断 >=10 成立,然后各自减 10,后执行的线程覆盖了前一个的结果,导致只减了一次。
这就是原子性被破坏:balance - amount 不是一步完成的,它分“读-改-写”三步,线程在中间被打断。