synchronized带来的问题 20110330

本文通过分析一个PLM系统的压力测试问题,揭示了不当使用synchronized关键字导致的性能瓶颈,并提出了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

很早就想弄个博客,与朋友们一起探讨技术。

 

就从一个客户的某个系统上线前的一点小问题谈起吧。

 

其实不算调优,算系统上线前的一个小插曲。 客户的技术人员W来邮件,说他们的PLM系统上线前用LoadRunner做压力测试时,出现了一个奇怪的现象。前两个小时内,ART比较平稳,始终在3-6秒之间。可2个小时过后,ART快速攀升到40-60秒,最高达100秒以上,且抖动得非常厉害。 未去之前的第一个感觉就是资源占用,出现了竞争。

 

到达现场,了解了系统运行环境。从JVM参数设置、应用服务器配置和数据库配置等方面进行了初步检查。发现JVM的内存回收策略存在一定问题,其它方面正常。调整了回收策略及其它相关参数后,系统性能略有10%以上的提升,但没从根本上解决问题。

 

通过Wily监测到执行缓慢的Top 10个JSP,结合业务调用逻辑和时间分析,发现每次压力测试2个小时后,基本处于同一时刻,部分页面运行突然变得缓慢。对调用栈进行逐个分析,发现执行时间激增的代码段对应有大量synchronized(session)和synchronized(request)代码。回头检查了客户的压力测试脚本,发现Vuser仅为1个。于是确认是同步块导致的问题。将Vuser参数化为实际最大并发数后重新运行压力测试程序,问题不再。

 

synchronized本用于同步控制,但不合理的压力测试脚本导致同一用户在多个并发访问时遭遇到了麻烦。虽然此例实际运行时不会出现这样的问题,但告诉我们慎用synchronized。我曾经看到某个客户的ERP系统中,最关键的获取经过封装的数据库连接处不恰当地使用了synchronized,导致并发量一上升就成了性能瓶颈。

 

大家可以回头看看Java编程中的单例模式的某种实现,想想synchronized为什么放在那个位置。

### 关于 `synchronized` 死锁的原因及解决方案 #### 原因分析 在 Java 中,当多个线程竞争共享资源时,如果某些线程持有某个锁并试图获取另一个锁,而其他线程恰好相反,则可能导致死锁。具体来说,`synchronized` 是通过隐式的监视器锁实现的,其核心在于 **互斥性和不可中断性**。一旦发生死锁,涉及的线程将永远处于等待状态。 以下是可能引发死锁的主要原因: 1. **循环依赖**:两个或更多线程互相等待对方释放锁[^1]。 2. **嵌套调用**:在一个已持有的锁内部尝试获取新的锁,如果没有合理的顺序控制,容易形成死锁[^3]。 3. **未定义的加锁顺序**:不同线程以不同的顺序锁定相同的资源集合,这可能会导致循环等待条件成立[^4]。 --- #### 解决方案 为了避免由 `synchronized` 导致的死锁问题,可以从以下几个方面入手: 1. **固定加锁顺序** 确保所有线程按照一致的顺序获取锁。例如,如果有两个对象 A 和 B 需要被锁定,那么所有的线程都应先锁定 A 后锁定 B。这样可以有效防止循环等待条件的发生[^4]。 ```java Object lockA = new Object(); Object lockB = new Object(); public void method() { synchronized (lockA) { // 总是先锁住 lockA System.out.println("Locked A"); synchronized (lockB) { // 再锁住 lockB System.out.println("Locked B after A"); } } } public void anotherMethod() { synchronized (lockA) { // 所有地方均遵循相同顺序 System.out.println("Locked A again"); synchronized (lockB) { System.out.println("Locked B after A again"); } } } ``` 2. **减少锁的作用范围** 尽量缩小同步代码块的粒度,只保护必要的临界区操作。过大的锁作用域不仅降低性能,还增加了死锁的可能性[^1]。 ```java private final Object lock = new Object(); public void updateState(int value) { synchronized (lock) { // 只对更新部分加锁 this.value += value; } } ``` 3. **利用显式锁(Lock 接口)替代 synchronized** JDK 提供了更灵活的 `ReentrantLock` 类型锁,支持超时机制和公平锁等功能,能够更好地处理复杂的并发场景。相比 `synchronized`,它可以提供更多的灵活性来规避潜在的死锁风险[^3]。 ```java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; Lock lockA = new ReentrantLock(); Lock lockB = new ReentrantLock(); public void tryAcquireWithTimeout() throws InterruptedException { boolean acquiredFirst = false, acquiredSecond = false; try { acquiredFirst = lockA.tryLock(500, TimeUnit.MILLISECONDS); if (!acquiredFirst) throw new IllegalStateException("Failed to acquire first lock"); acquiredSecond = lockB.tryLock(500, TimeUnit.MILLISECONDS); if (!acquiredSecond) throw new IllegalStateException("Failed to acquire second lock"); // Critical section here... } finally { if (acquiredFirst) lockA.unlock(); // Always release locks in reverse order of acquisition. if (acquiredSecond) lockB.unlock(); } } ``` 4. **引入定时退出策略** 如果无法完全避免死锁,可以在设计阶段加入检测逻辑或者设置最大等待时间。例如,使用 `tryLock()` 方法代替传统的阻塞式锁请求。 --- #### Java 多线程死锁案例 下面是一个典型的死锁例子及其改进版本: ##### 案例描述 假设有两个账户 AccountA 和 AccountB,分别属于两个人 Alice 和 Bob。他们之间存在转账需求,但由于不恰当的加锁方式,最终引发了死锁。 ##### 错误实现 ```java class BankAccount { double balance; public synchronized void transfer(BankAccount target, double amount) { if (balance >= amount) { balance -= amount; // 修改当前余额 Thread.sleep(10); // 模拟延迟 target.balance += amount; // 更新目标账户余额 } else { System.out.println("Insufficient funds!"); } } } public class DeadlockExample { public static void main(String[] args) throws Exception { BankAccount accountA = new BankAccount(); BankAccount accountB = new BankAccount(); Runnable taskAlice = () -> accountA.transfer(accountB, 100); Runnable taskBob = () -> accountB.transfer(accountA, 50); Thread aliceThread = new Thread(taskAlice); Thread bobThread = new Thread(taskBob); aliceThread.start(); bobThread.start(); aliceThread.join(); bobThread.join(); } } ``` 在这个错误实现中,Alice 和 Bob 的线程各自持有一个账户上的锁,并试图去访问另一方的账户,从而造成死锁。 ##### 改进后的实现 采用固定的加锁顺序解决问题: ```java class FixedBankAccount { double balance; public void safeTransfer(FixedBankAccount target, double amount) throws InterruptedException { int idSelf = System.identityHashCode(this); int idTarget = System.identityHashCode(target); Object firstLock = idSelf < idTarget ? this : target; Object secondLock = idSelf < idTarget ? target : this; synchronized (firstLock) { synchronized (secondLock) { if (this.balance >= amount) { this.balance -= amount; Thread.sleep(10); // Simulate delay target.balance += amount; } else { System.out.println("Not enough money."); } } } } } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值