核心内容摘要
【系统重生指南】重装Windows后,三步复原Anaconda与Jupyter Notebook
文章目录Java多线程synchronized方法独占锁的秘密什么是synchronizedsynchronized背后的独占锁机制
独占锁的定义
锁的粒度
锁的获取与释放独占锁的优缺点优点缺点
案例分析synchronized方法的
常见问题例子代码问题分析解决方案方法一细化锁粒度方法二使用更高效的锁机制
总结通过合理使用synchronized方法和其他并发工具如ReentrantLock我们可以写出既安全又高效的多线程代码。
领取 | 1000 套高质量面试题大合集无套路闫工带你飞一把Java多线程synchronized方法独占锁的秘密大家好欢迎来到闫工的博客今天我们要聊的是一个非常经典的话题——Java多线程中的synchronized关键字以及它背后的“独占锁”机制。
作为一个Java开发者你可能每天都在用synchronized但你真的了解它的秘密吗别急咱们一步步来拆解这个知识点。
什么是synchronized首先synchronized是Java中用来实现线程同步的一个关键字。
简单来说它用于确保在多线程环境下同一时间只有一个线程可以执行某个方法或代码块。
这样就能避免多个线程同时修改共享资源时出现的竞态条件Race Condition和不一致的问题。
举个栗子假设你有一个银行账户余额是100元。
如果有两个线程同时尝试从这个账户中取钱可能会发生以下情况线程A读取到余额是100元并决定取出50元。
同时线程B也读取到余额是100元并取出30元。
这样就会导致最终的余额变成20元100 - 50 - 30但实际上这是错误的因为两个线程都操作了同一个资源。
为了避免这种情况synchronized可以帮助我们实现“独占锁”确保同一时间只有一个线程可以操作这个资源。
synchronized背后的独占锁机制那么问题来了synchronized是如何实现这种“独占锁”的呢让我们从底层原理来理解这个问题。
独占锁的定义在Java中“独占锁”指的是在同一时间只有一个线程可以持有该锁并执行被保护的方法或代码块。
当一个线程进入synchronized方法时它会获取到这把“锁”其他尝试进入这个方法的线程必须等待直到当前线程释放锁。
锁的粒度在Java中synchronized可以修饰两种类型的
代码实例方法和静态方法。
它们分别对应不同的锁粒度实例方法锁是针对实例对象的。
也就是说每个实例对象都有自己的锁。
静态方法锁是针对类的。
无论有多少个实例对象所有线程在调用同一个类的静态synchronized方法时都会共享同一把锁。
举个例子publicclassAccount{privatedoublebalance;// 实例synchronized方法publicsynchronizedvoidwithdraw(doubleamount){if(balanceamount){balance-amount;}}// 静态synchronized方法publicstaticsynchronizedvoidtransfer(Accountfrom,Accountto,doubleamount){from.withdraw(amount);to.deposit(amount);}}在这个例子中withdraw方法是实例方法每个Account对象都有自己的锁。
transfer方法是静态方法所有线程在调用这个方法时都会争夺同一个类级别的锁。
锁的获取与释放当一个线程进入synchronized方法时它会尝试获取对应的锁。
如果锁没有被其他线程持有那么当前线程就会成功获取锁并执行方法否则该线程会被阻塞直到锁被释放。
释放锁的过程发生在以下几种情况当前线程正常退出synchronized方法。
当前线程在synchronized方法中抛出异常。
当前线程调用了wait()方法并释放了锁后面会详细讲解。
独占锁的优缺点了解了独占锁的基本原理后我们再来分析它的优缺点。
优点简单易用synchronized关键字内置在Java语言中使用起来非常方便。
自动管理Java虚拟机JVM会自动管理锁的获取与释放开发者不需要手动实现复杂的锁机制。
可重入性一个线程可以多次进入同一个synchronized方法而不会被阻塞。
缺点粒度较粗synchronized通常会对整个方法进行加锁这可能会导致不必要的性能开销。
如果只是其中一小部分代码需要同步使用synchronized可能会显得过于“保守”。
死锁风险多个线程在争夺多把锁时可能会发生死锁Deadlock。
例如线程A持有锁1并试图获取锁2而线程B持有锁2并试图获取锁1就会导致双方都无法继续执行。
案例分析synchronized方法的
常见问题为了更好地理解synchronized的实际应用和潜在问题我们来看一个经典的案例——银行转账。
例子代码publicclassBank{publicsynchronizedvoidtransfer(Accountfrom,Accountto,doubleamount){if(from.getBalance()amount){from.withdraw(amount);// 模拟网络延迟try{Thread.sleep(
;}catch(InterruptedExceptione){e.printStackTrace();}to.deposit(amount);}}publicstaticvoidmain(String[]args){BankbanknewBank();AccountalicenewAccount(Alice,
100.
;AccountbobnewAccount(Bob,
50.
;// 创建两个线程同时执行转账Threadthread1newThread(()-{bank.transfer(alice,bob,
30.
;});Threadthread2newThread(()-{bank.transfer(bob,alice,
40.
;});thread
start();thread
start();}}classAccount{privateStringname;privatedoublebalance;publicAccount(Stringname,doublebalance){this.namename;this.balancebalance;}publicsynchronizedvoidwithdraw(doubleamount){if(balanceamount){balance-amount;System.out.println(name withdrew: amount);}}publicsynchronizedvoiddeposit(doubleamount){balanceamount;System.out.println(name deposited: amount);}publicdoublegetBalance(){returnbalance;}}问题分析在上面的代码中Bank类中的transfer方法是synchronized的这意味着每次只能有一个线程执行转账操作。
然而这个实现仍然存在一些潜在的问题粒度问题transfer方法对整个转账过程进行了加锁但实际上只需要对账户的操作进行同步。
性能瓶颈如果多个线程频繁调用transfer方法由于独占锁的存在可能会导致排队和性能下降。
解决方案为了优化这个问题我们可以考虑以下几种方法方法一细化锁粒度将锁粒度从整个transfer方法降低到具体的账户操作。
例如在Account类中对每个操作进行加锁。
修改后的代码publicclassBank{publicvoidtransfer(Accountfrom,Accountto,doubleamount){if(from.getBalance()amount){synchronized(from){from.withdraw(amount);}try{Thread.sleep(
;}catch(InterruptedExceptione){e.printStackTrace();}synchronized(to){to.deposit(amount);}}}}方法二使用更高效的锁机制在Java中除了内置的synchronized关键字还可以使用ReentrantLock来实现更灵活和高效的锁管理。
修改后的代码importjava.util.concurrent.locks.ReentrantLock;publicclassBank{privatefinalReentrantLocklocknewReentrantLock();publicvoidtransfer(Accountfrom,Accountto,doubleamount){if(from.getBalance()amount){try{// 尝试获取锁参数为true表示阻塞等待lock.lock();from.withdraw(amount);Thread.sleep(
;to.deposit(amount);}catch(InterruptedExceptione){e.printStackTrace();}finally{// 释放锁lock.unlock();}}}publicstaticvoidmain(String[]args){BankbanknewBank();AccountalicenewAccount(Alice,
100.
;AccountbobnewAccount(Bob,
50.
;Threadthread1newThread(()-{bank.transfer(alice,bob,
30.
;});Threadthread2newThread(()-{bank.transfer(bob,alice,
40.
;});thread
start();thread
start();}}
总结synchronized关键字是Java中实现线程同步的一种简单而有效的方式但它也有一些局限性。
在实际开发中我们需要根据具体场景选择合适的锁粒度和锁机制以避免性能瓶颈和潜在的死锁问题。
通过合理使用synchronized方法和其他并发工具如ReentrantLock我们可以写出既安全又高效的多线程代码。
领取 | 1000 套高质量面试题大合集无套路闫工带你飞一把成体系的面试题无论你是大佬还是小白都需要一套JAVA体系的面试题我已经上岸了你也想上岸吗闫工精心准备了程序准备面试想系统提升技术实力闫工精心整理了1000 套涵盖前端、后端、算法、数据库、操作系统、网络、设计模式等方向的面试真题 详细解析并附赠高频考点