大家好,我是小米,在日常的开发工作中,锁是我们常常会使用到的概念。在 Java 中,synchronized 和 lock 都是用于多线程同步的关键字和类。今天,我就来为大家详细介绍一下synchronized 与 lock 的区别,包括从存在层次、锁的获取方式、锁的释放、锁类型、性能、支持锁的场景等十个区别点,并通过两个电商项目的实际案例来演示它们的区别。
synchronized 与 lock 的区别
- 存在层次:synchronized 是 Java 语言内置的关键字,属于 JVM 层面的锁;而 lock 是 Java 类库提供的一个类,属于 Java 语言层面的锁。
- 锁的获取方式:synchronized 的获取方式是隐式的,即在进入同步代码块或方法时自动获取锁,并在退出时自动释放锁;而 lock 的获取方式是显式的,需要手动调用lock() 方法获取锁,并在使用完后手动调用 unlock() 方法释放锁。
- 锁的释放:synchronized 的锁是在出现异常或者执行完同步代码块或方法后自动释放的;而 lock 需要在 finally 块中手动调用 unlock() 方法释放锁,否则可能导致死锁。
- 锁类型:synchronized 只有一种类型的锁,即互斥锁,它是非公平锁;而 lock 提供了多种类型的锁,包括公平锁和非公平锁。
- 性能:synchronized 是 JVM 内置的锁,效率相对较低,因为它会涉及到用户态和内核态的切换;而 lock 是Java 类库提供的锁,性能较高,因为它使用了更底层的硬件级别的实现。
- 支持锁的场景:synchronized 只支持在代码块和方法上加锁;而 lock 支持更灵活的加锁和释放方式,例如可以在任意位置加锁和释放锁,支持多个条件变量的使用。
- 可重入性:synchronized 是可重入锁,即同一线程可以多次获取同一把锁而不会死锁;而 lock 也是可重入锁,但需要注意要手动调用相同次数的 unlock() 方法才能完全释放锁。
- 等待通知机制:synchronized使用的是wait()和notify()/notifyAll() 方法实现线程之间的等待和通知机制;而 lock 使用的是 Condition 对象来实现类似的功能。
- 可见性:synchronized 在进入同步代码块时会自动获取锁并刷新线程的工作内存,保证了线程间的可见性;而lock 需要手动使用 volatile 关键字或者显式调用 lock() 和 unlock() 方法来保证可见性。
- 锁的粒度:synchronized 是对整个对象进行加锁的,即当一个线程获得了某个对象的锁后,其他线程无法获得该对象的任何锁;而 lock 可以实现更细粒度的锁定,例如可以对对象的某个属性或者某一段代码块进行加锁,从而提高并发性能。
synchronized的加锁流程
在 Java 中,synchronized 锁可以分为三种类型:偏向锁、轻量级锁和重量级锁。偏向锁适用于无竞争的情况,轻量级锁适用于短时间内竞争不激烈的情况,重量级锁适用于竞争激烈或者竞争时间较长的情况。
- 偏向锁:当一个线程访问同步代码块时,会先检查对象头中的 Mark Word,如果发现没有被其他线程锁定,就会将当前线程 ID 记录到 Mark Word 中,并将 Mark Word 设置为偏向锁。以后该线程再次访问同步代码块时,无需重新获取锁,而是直接使用偏向锁,从而避免了大部分的同步操作,提高了性能。
- 轻量级锁:当多个线程同时访问同步代码块时,会发生轻量级锁的竞争。这时,JVM 会尝试将对象头中的 Mark Word 设置为轻量级锁,并将当前线程的 ID 记录在锁记录(Lock Record)中。如果竞争不激烈,当前线程可以顺利获取轻量级锁,执行同步操作;如果竞争激烈,获取轻量级锁失败,会升级为重量级锁。
- 重量级锁:当轻量级锁竞争失败或者锁对象已经被设置为重量级锁时,会升级为重量级锁。重量级锁使用操作系统的互斥量来实现,会导致线程的阻塞和唤醒,性能较差。
需要注意的是,锁的升级是单向的,即锁一旦升级为重量级锁,就无法再降级为轻量级锁或者偏向锁。
实际案例
下面,我通过两个电商项目的实际案例来演示 synchronized 和lock 的区别。
使用synchronized:假设在电商项目中,存在一个订单处理的方法,多个线程同时调用该方法进行订单处理。
在这个案例中,使用了synchronized 关键字对processOrder() 方法进行了加锁,保证了订单处理的线程安全性。当一个线程获得了对象的锁后,其他线程无法获得该对象的锁,从而保证了同一时刻只有一个线程能够执行订单处理逻辑。然而,由于 synchronized 是重量级锁,可能会导致性能下降,特别是在高并发的情况下。
使用Lock:
在这个案例中,使用了 Lock 接口和 ReentrantLock 类来实现加锁和解锁的操作。与 synchronized 不同的是,使用 Lock时,我们需要手动调用 lock() 方法来获取锁,并在 finally 块中调用 unlock() 方法来释放锁。这样可以实现更细粒度的锁定,从而提高了并发性能。
END
综上所述,synchronized 和 Lock 都可以用于实现线程的同步,但在使用方式、性能和灵活性等方面存在一些区别。根据具体的场景和需求,选择合适的锁机制对于保障线程安全和提高并发性能非常重要。希望通过本文的介绍,能够对 synchronized和Lock的区别有更加清晰的认识。如果你对这方面的知识还有疑问或者有其他技术问题想要分享讨论,欢迎在评论区留言,我会及时回复。谢谢!