Java synchronized 怎么用

你有没有过这种经历:在奶茶店下单时,明明只有3个人排队,结账系统却偶尔会同时给两个人打出同一杯奶茶的取餐码?或者你用手机抢演唱会门票,明明只放了100张票,却显示有200人抢成功?其实这些“混乱”的场景,大多是因为多个操作同时抢同一资源时没做好“排队规则”,而Java里的synchronized,就是专门给这种场景定“排队规则”的工具。

先搞懂:synchronized到底是啥?

咱们举个最生活化的例子:你和朋友同时用一台充电宝共享柜借充电宝。要是没有规则,你刚打开柜门,朋友也伸手拉开,两个人可能同时抓住同一台充电宝,把接口扯坏;要是有了“同一时间只能一个人开柜门拿”的规则,就不会乱了。synchronized干的就是这个事:保证同一个代码块,同一时间只能被一个线程(可以理解成“独立干活的小进程”)执行,避免多个线程乱抢资源。

Java synchronized 怎么用

最常用的2种用法:别搞混场景

synchronized的用法其实没那么复杂,核心分两种,对应你要“锁谁”——要么锁方法,要么锁代码块,看你的需求来选:

**锁普通方法:管“当前对象”的所有操作**

假设你开了一家小超市,收银台就是一个普通方法,所有顾客(线程)都要收银台结账。你给这个方法加synchronized,就等于定了“同一时间只能一个人在这个收银台结账”的规则。举个代码例子(不用怕看不懂,看场景就行):

`java

public class Supermarket {

// 超市的收银台(普通方法)

public synchronized void checkout(String customer) {

System.out.println(customer + "正在结账,其他人等会儿");

// 模拟结账耗时1秒

try { Thread.sleep(1000); } catch (InterruptedException e) {}

System.out.println(customer + "结账完成,下一位来吧");

}

}

`

要是有3个顾客同时来,第一个会占住收银台,后两个只能排队,不会出现同时结账的混乱。但要注意:如果是两个不同的超市(两个Supermarket对象),那它们的收银台是分开的,你在第一个超市结账,不影响第二个,这就是普通方法锁的是“当前对象”。

**锁代码块:只锁“需要保护的小部分”**

要是你不想整个方法都被锁住,只锁里面的关键操作,就用代码块。还是超市的例子:超市里有“存包处”,存包的地方才需要锁,选商品的地方不用。这时候就可以把存包的代码块用synchronized包起来,只让一个人存包,选货的人可以同时逛。

代码例子:

`java

public class Supermarket {

// 存包用的柜子对象,作为锁的“钥匙”

private final Object locker = new Object();

public void shop(String customer) {

// 选商品不用锁,所有人可以同时逛

System.out.println(customer + "正在选零食,不用排队");

// 存包需要锁,同一时间只能一个人存

synchronized (locker) {

System.out.println(customer + "正在存包,其他人等");

try { Thread.sleep(500); } catch (InterruptedException e) {}

System.out.println(customer + "存包完成");

}

}

}

`

这种用法更灵活,比如你要保护的不是整个对象,只是某段代码,用锁代码块就不会浪费性能。

用的时候要注意这2个坑

很多人刚用synchronized会踩小坑,记住这两点就不容易出错:

第一,锁的“钥匙”不能乱选:比如锁普通方法时,默认的钥匙是当前对象,要是你锁代码块用了String这种特殊对象(比如new String("lock")),可能会出现“假锁”——因为JVM会把相同内容的String当成同一个,两个不同的线程用相同的String当钥匙,反而会互相等待,乱上加乱。

第二,别锁太轻或太重:要是锁的范围太小,比如只锁一行代码,等于没加规则;要是锁的范围太大,比如整个方法都锁,会让其他线程一直等,程序变慢。比如你在超市里,要是把“选商品”也锁了,那所有人只能一个一个逛,效率就低了。

其实说白了,synchronized就是Java给多线程场景定的“排队规则”,选对锁的方式,别乱选钥匙,就能解决大部分多线程混乱的问题。

上一篇 JavaScript 如何配置 Prettier