首页 > 编程语言 >Java中的synchronized关键字是如何工作的?

Java中的synchronized关键字是如何工作的?

时间:2024-03-22 20:33:27浏览次数:33  
标签:同步 Java synchronized 获取 关键字 死锁 线程

在Java中,synchronized关键字是一种内置的同步机制,用于控制多个线程对共享资源的访问,以防止出现数据不一致和竞态条件。当一个线程进入一个synchronized块或方法时,它需要获取一个锁(也称为监视器锁或互斥锁),如果锁已经被其他线程持有,则该线程将被阻塞,直到锁被释放。

以下是synchronized关键字的工作原理的详细说明:

1.Synchronized方法

  • 当一个线程调用一个synchronized方法时,它必须获得该方法所在对象的锁才能执行该方法。
  • 如果锁已经被其他线程持有,调用线程将被放入锁的等待队列中,并被阻塞。
  • 当持有锁的线程完成synchronized方法并释放锁时,等待队列中的一个线程将被唤醒并获得锁,然后执行该方法。

2.Synchronized块

  • synchronized块允许你更精确地控制哪些代码需要同步。
  • 你可以在synchronized块中指定一个对象作为锁的对象。当线程进入该块时,它必须获得该对象的锁。
  • 如果锁已经被其他线程持有,线程将被阻塞,直到锁被释放。

3.锁的释放

  • 当线程完成synchronized方法或块中的代码时,它将自动释放锁。
  • 如果在synchronized方法或块中发生异常,并且该异常没有被捕获,则JVM将确保锁被释放,以防止死锁。
  • 锁是可重入的,这意味着一个线程可以多次获得同一个锁而不会死锁。但是,每次获得锁都必须有相应的释放。

4.性能考虑

  • synchronized关键字引入了一定的性能开销,因为线程必须等待锁的释放和获取。
  • 在高并发环境中,过度使用synchronized可能导致性能瓶颈。
  • 因此,最好只在确实需要同步的地方使用synchronized,并考虑使用其他并发控制机制(如java.util.concurrent包中的工具)来优化性能。

5.与volatile关键字的比较

  • volatile关键字确保变量的可见性,即当一个线程修改了volatile变量的值,其他线程能立即看到修改。但它不保证原子性。
  • synchronized既确保可见性又确保原子性,但性能开销更大。
  • 在某些情况下,可以结合使用volatilesynchronized来实现更高效的同步。

6. 锁升级

  • 在JVM中,为了优化synchronized的性能,锁的实现可能会经历多种状态的升级:无锁、偏向锁、轻量级锁和重量级锁。
    • 偏向锁:为了减少无竞争情况下的解锁和重加锁操作,JVM引入了偏向锁。当一个线程首次访问synchronized块时,它会在对象头中记录下当前线程的ID,这样下次该线程再访问时无需进行锁操作。
    • 轻量级锁:当线程A再次尝试获得之前由线程B持有的偏向锁时,偏向锁就会升级为轻量级锁。此时,线程B会释放锁,并将对象头的Mark Word设置为指向锁记录的指针,线程A则尝试获得这个锁。
    • 重量级锁:如果轻量级锁的竞争变得激烈,锁就会升级为重量级锁。此时,锁的获取和释放操作会由操作系统的互斥量(mutex)来实现,这会带来更大的性能开销。

7. 锁膨胀

  • 当一个对象被多个线程频繁地争用时,它可能会导致锁膨胀,即锁的升级过程可能会频繁发生,从而降低性能。
  • 为了避免这种情况,可以尝试减少锁的竞争,例如通过更细粒度的锁(如锁分段)或使用无锁数据结构。

8. 死锁避免

  • 使用synchronized时需要注意避免死锁。死锁通常发生在多个线程相互等待对方释放锁的情况下。
  • 为了避免死锁,可以遵循一些最佳实践:
    • 顺序锁:始终以相同的顺序获取锁。这可以防止发生循环等待条件,这是死锁发生的必要条件之一。
    • 锁超时:尝试获取锁时设置一个超时时间。如果在这个时间内无法获得锁,线程将放弃并尝试其他操作或稍后重试。然而,Java的synchronized关键字本身不支持超时机制,但可以通过java.util.concurrent.locks.Lock接口的实现(如ReentrantLock)来实现这一功能。
    • 锁粒度:尽量减小锁的粒度,即只锁定必要的代码部分和数据结构,以减少线程之间的竞争。

9. 锁性能分析

  • 使用JVM的性能分析工具(如JProfiler、VisualVM等)可以帮助你识别和解决与synchronized相关的性能问题。
  • 这些工具可以提供关于线程争用、锁等待时间和锁持有时间等信息,从而帮助你优化同步策略。

10. 替代方案

  • 虽然synchronized是Java中内置的同步机制,但在某些情况下,你可能希望考虑使用其他并发控制工具来优化性能或简化代码。
  • java.util.concurrent.locks包提供了更灵活的锁实现,如ReentrantLock(可重入锁)、ReadWriteLock(读写锁)等。这些锁提供了更丰富的功能,如支持锁的超时、可中断的锁获取操作等。
  • java.util.concurrent.atomic包中的原子变量类(如AtomicIntegerAtomicLong等)提供了一种无锁的方式来更新共享变量。这些类使用底层的硬件支持来实现原子操作,从而避免了锁的开销。然而,它们只适用于简单的数据类型和更新操作。对于更复杂的同步需求,仍然需要使用锁或其他同步机制。

11. 锁剥离与锁消除

  • 锁剥离:在某些情况下,编译器或JVM可能能够确定一个synchronized块内的某些代码实际上并不需要同步。这时,JVM可能会将这些代码移出同步块,从而减少锁的持有时间,提高性能。然而,这是一个非常高级的优化,依赖于具体的JVM实现和编译器的智能程度。
  • 锁消除:当JVM检测到某个synchronized块或方法实际上没有被多个线程访问时(即没有发生竞争),它可能会完全消除这个锁。这种优化是通过逃逸分析(escape analysis)来实现的,逃逸分析可以判断对象的作用域是否仅限于当前线程。如果是这样,那么同步操作就是多余的,可以被安全地移除。

12. 使用synchronized的注意事项

  • 避免在持有锁时执行IO操作:IO操作(如网络请求或磁盘读写)通常是不确定的,并且可能需要很长时间才能完成。如果一个线程在持有锁的同时执行IO操作,其他需要该锁的线程将被迫等待,即使它们并不依赖这个IO操作的结果。这会导致性能下降和潜在的死锁风险。
  • 减少锁的持有时间:尽量只在确实需要的代码段上同步,以减少锁的持有时间。这可以通过细化同步块的范围来实现。例如,避免在循环内部持有锁,而是将循环体内的部分代码移出同步块。
  • 避免在锁定的代码中调用外部方法:当在synchronized块或方法中调用外部方法时(尤其是那些你没有控制的库方法),你无法确保这些方法不会执行耗时的操作或尝试获取其他锁。这可能会导致死锁或其他并发问题。如果必须调用外部方法,请考虑在调用之前释放锁。
  • 谨慎使用嵌套锁:如果一个线程在持有一个锁的同时尝试获取另一个锁,就可能出现死锁。即使两个锁由不同的对象持有,也应该避免这种情况,因为其他线程可能需要以相反的顺序获取这些锁。使用“锁顺序”规则可以帮助避免这种情况,即总是以相同的顺序请求锁。

13. synchronizedReentrantLock的比较

  • synchronized是Java语言内置的同步机制,简单易用,适用于大多数同步场景。它会自动释放锁,因此在发生异常时不会导致锁泄露。然而,它的功能相对有限,不支持锁的超时和可中断的锁获取操作。
  • ReentrantLockjava.util.concurrent.locks包提供的一个可重入的互斥锁实现。与synchronized相比,它提供了更丰富的功能,如支持锁的超时、可中断的锁获取操作以及能够查询锁的状态(是否被锁定、是否由当前线程持有等)。然而,使用ReentrantLock需要显式地释放锁(通常在finally块中),否则可能导致锁泄露。因此,在使用ReentrantLock时需要更加小心谨慎。

14. 锁的重入性

  • synchronized关键字和ReentrantLock都支持锁的重入性,这意味着同一个线程可以多次获得同一个锁而不会导致死锁。这对于需要在多个方法或代码块中保持对共享资源的连续访问的情况非常有用。
  • 在使用重入锁时,需要注意确保每次获得锁后都有相应的释放操作,以避免锁泄露。对于synchronized,这通常是自动处理的;对于ReentrantLock,你需要在代码中显式地释放锁。

15. 锁的公平性

  • 锁可以是公平的也可以是不公平的。公平锁按照线程请求锁的顺序来分配锁,而不公平锁则不保证按照任何特定的顺序来分配锁。Java中的synchronized关键字实现的是不公平锁。
  • ReentrantLock的构造函数允许你选择锁的公平性。公平锁通常可以减少线程饥饿的可能性(即某些线程长时间得不到锁的情况),但可能会降低整体性能,因为需要维护一个等待队列。

16. 使用条件变量

  • synchronized关键字与wait()notify()/notifyAll()方法结合使用时,可以实现线程间的协作和通信。这些方法允许线程在特定条件下等待或唤醒其他线程。
  • 然而,使用这些方法时需要特别小心,因为它们是低级别的并发原语,容易出错。常见的错误包括死锁、活锁(线程无休止地重试而不是等待)和丢失信号(一个线程发出信号但没有其他线程在等待)。
  • 作为替代方案,你可以考虑使用java.util.concurrent包中的高级并发工具,如SemaphoreCountDownLatchCyclicBarrierPhaser,它们提供了更清晰、更易于使用的线程协作机制。

17. 避免活锁和饥饿

  • 活锁:当线程无休止地改变状态以尝试解决资源争用时,可能会发生活锁。例如,两个线程可能都在尝试获取两个锁(A和B),但总是以不同的顺序获取(一个线程先获取A再获取B,而另一个线程先获取B再获取A),导致它们永远无法同时获得两个锁。为了避免活锁,可以实施一种策略来确保线程总是以相同的顺序请求锁。

  • 饥饿:在某些情况下,一个或多个线程可能因为其他贪婪的线程而无法获得足够的资源。为了避免饥饿,可以使用公平锁或其他调度策略来确保所有线程都有机会访问共享资源。此外,也可以考虑使用优先级调度器来赋予某些线程更高的优先级。然而,需要注意的是,优先级调度并不总是能够完全解决饥饿问题,并且可能引入其他并发问题(如优先级反转)。

总之,synchronized关键字是Java中一种重要的同步机制,用于保护共享资源免受并发访问的干扰。但是,它也需要谨慎使用,以避免性能问题和死锁等并发问题,在使用synchronized关键字进行并发编程时,需要仔细考虑锁的范围、粒度、顺序以及与其他线程的交互方式。同时,了解并熟悉Java提供的其他并发工具和库也是非常重要的,因为它们可以帮助你编写更高效、更易于理解和维护的并发代码。

标签:同步,Java,synchronized,获取,关键字,死锁,线程
From: https://blog.csdn.net/m0_37638307/article/details/136905123

相关文章

  • .lastUpdated:The POM for mysql:mysql-connector-java:jar:8.1.0 is missing, no depe
    描述:在IDEA中,出现该类报错,查看本地仓库中项目对应的jar包存在,却无法获取时,可能是文件中生成.lastUpdated文件或有remote.repositories文件导致的。.lastUpdated:在更新maven项目的时候,每一个jar包路径下的_remote.repositories文件都会同setting.xml中设置的仓库地址id......
  • 房屋租赁系统(JSP+java+springmvc+mysql+MyBatis)
    本项目包含程序+源码+数据库+LW+调试部署环境,文末可获取一份本项目的java源码和数据库参考。项目文件图项目介绍随着城市化进程的加快和人口流动性增大,房屋租赁市场日益繁荣,对租赁信息的管理提出了更高要求。一个高效的房屋租赁系统能够为房东和租户提供一个便捷的信息发布......
  • 数据爬取关键字——UA伪装
     1importrequests23#处理路径45#url='https://cn.bing.com/search?q=python%E7%88%AC%E5%8F%96%E7%BD%91%E9%A1%B5%E6%95%B0%E6%8D%AE'6#这里复制粘贴过来会变成乱码没关系吧乱码后面的修改一下就行了78#UA:伪装9#user_agent门户网站的服务......
  • Java编程经典例题|水仙花数
     一、题目描述水仙花数(NarcissisticNumber)也被称为阿姆斯特朗数(ArmstrongNumber),它是一个n位数,其各位数字的n次方之和等于该数本身。例如,对于三位数的水仙花数,其定义是:一个三位数,它的每个位上的数字的3次幂之和等于它本身。例如,153是一个水仙花数,因为1^3+5^3+3^3=153......
  • Java基础面试题(一)
    1.解释下什么是面向对象?面向对象和面向过程的区别?面向对象(Object-Oriented,简称OO)是一种程序设计范式或编程范式,也是一种程序开发的方法。它将对象作为程序的基本单元,将程序和数据封装在对象中,以提高软件的可重用性、灵活性和扩展性。在面向对象编程中,有以下几个核心概念:......
  • Java基础面试题(四)
    1.深克隆和浅克隆的区别?深克隆和浅克隆的主要区别在于它们处理对象中的引用类型字段的方式不同,这导致它们在复制对象时的行为有所不同。浅克隆(ShallowClone)在复制对象时,对于非基本类型(即引用类型)的属性,只复制其引用地址,而不复制引用的对象本身。这意味着,原始对象和克隆......
  • [项目] Java + Servlet + MySql + BootStrap4 一个简单的购书网(网上书城)项目 (附源码)
    ......
  • Java之集合
    一.List        存取有序,可以存储重复的元素,可以用下标进行元素的操作1.ArrayList        在Java数组中,长度是固定的,因此在数组被创建后,不能修改长度,这意味着开发者需要实现知道数组的长度。但在一般情况下,只有在运行时才知道数组长度。为了解决这个问题,Ar......
  • 关于javaScript的计算精度的解决办法
    项目中我们常常需要做一些计算,由于浮点数的二进制表示可能不精确,经常会遇到计算精度问题,例letresultNum=0.1+0.2;console.log(resultNum);//0.30000000000000004这个时候,如果我们不单独处理,那么页面上展示的时候就出现布局错乱等问题,比如我们可以保留两位小数采用Number(r......
  • Java版本spring cloud + spring boot企业电子招投标系统源代码
    招投标管理系统是一个集门户管理、立项管理、采购项目管理、采购公告管理、考核管理、报表管理、评审管理、企业管理、采购管理和系统管理于一体的综合性应用平台。它适用于招标代理、政府采购、企业采购和工程交易等业务的企业,旨在提高项目管理的效率和质量。该系统以项目为主......