🌰 场景:全家共用一台洗衣机(共享资源)
假设你家有一台洗衣机,你和你爸妈三个人都想用它洗衣服(相当于3个线程)。
但洗衣机一次只能洗一家人的衣服(共享资源一次只能被一个“线程”使用),这时候就需要“排队”,不然会乱套。
没有 synchronized
的情况:
你刚把衣服放进洗衣机,按下“开始”按钮,还没等关门,你爸突然按了“暂停”想放他的衣服,你妈同时按了“脱水”……
结果:洗衣机可能报错,衣服洗不干净(数据混乱)。
有 synchronized
的情况:
洗衣机自带一个“门锁”功能:当一个人开始使用时,会自动锁门,其他人必须等里面的人用完、开门后才能用。
- 你打开洗衣机,放衣服 → 关门并上锁(获取锁)→ 开始洗衣服(执行同步代码)。
- 你爸和你妈看到门锁着,只能在门口等着(阻塞)。
- 你洗完后,开门解锁(释放锁),下一个人才能用。
🚪 synchronized
的核心:给资源加一把“互斥锁”
它的作用就是:保证同一时间只有一个线程能“进入”被它保护的代码块(或方法),其他线程必须排队等待。就像上厕所时锁门,别人只能等你出来。
三种常见用法,继续用洗衣机举例:
1. 锁整个方法(实例方法)
public class Washer {
// 把整个方法变成“带锁的房间”
public synchronized void wash() {
// 洗你的衣服(操作共享资源)
}
}
- 相当于:洗衣机的门就是锁,调用
wash()
方法时自动锁门,方法结束后自动开门。 - 多个线程调用同一个
Washer
实例的wash()
时,必须排队。
2. 锁静态方法(类级别的锁)
如果洗衣机是“共享设备”(比如小区的公共洗衣机,所有住户共用),这时候锁的是整个“洗衣机类”,而不是某个实例:
public class PublicWasher {
// 静态方法,锁的是 PublicWasher 这个类(所有实例共享一把锁)
public static synchronized void wash() {
// 洗公共衣服
}
}
- 相当于:不管多少台公共洗衣机,只要有一个住户在用,其他住户都得等(因为锁在“公共洗衣机”这个类别上)。
3. 锁代码块(精确控制锁的范围)
如果你只想给“脱水”环节加锁(比如脱水时不能开门),而不是整个洗衣过程:
public class Washer {
private Object脱水锁 = new Object(); // 自定义一个“锁对象”
public void wash() {
// 洗、漂、脱水中,只有脱水需要加锁
synchronized (脱水锁) { // 给这段代码加锁
执行脱水程序();
}
// 其他步骤不用等,可以并行
}
}
- 相当于:只有脱水时锁门,洗衣服时门可以开(其他线程可以做别的事),更灵活。
❓ 为什么需要 synchronized
?举个程序员能懂的例子
假设两个线程同时给一个账户转账(共享变量 balance
):
int balance = 100; // 初始余额
// 线程A:存50元
balance = balance + 50;
// 线程B:取30元
balance = balance - 30;
如果没有 synchronized
,可能出现:
- 线程A刚读取到
balance=100
,还没加50,就被线程B打断。 - 线程B读取到
balance=100
,算出70并写入。 - 线程A继续执行,算出150并写入。
最终余额变成150(正确应该是120),数据出错!
加锁后:
synchronized (账户) { // 锁定账户对象
balance = balance + 50; // 线程A执行完,释放锁
}
// 线程B才能进来执行
synchronized (账户) {
balance = balance - 30;
}
保证每次只有一个线程操作 balance
,结果就正确了。
✨ 总结
synchronized
就像一把“独占锁”:
- 作用:确保共享资源在同一时间只能被一个线程使用,避免混乱。
- 效果:排队执行,先到先得,用完释放。
- 场景:多个线程操作同一个“东西”(比如变量、文件、数据库)时,必须用它来保证数据安全。
记住:它的核心就是“互斥”,就像你用洗衣机时锁门,别人只能等你用完~