首页 - 技巧 - 锁的分类

锁的分类

2023-10-09 06:36

锁的7大分类

  • 偏置锁/轻型锁/重型锁
  • 公平锁/非公平锁
  • 可中断锁/不间断锁
  • 悲观锁/乐观锁
  • 可重入锁/不可重入锁
  • 共享锁/独占锁
  • 自旋锁/非自旋锁

偏置锁/轻型锁/重型锁

这三种锁具体是指同步锁的状态,由对象头中的mark字来表示。

  1. 偏置锁

如果这个锁自始至终都没有竞争,那么其实不需要加锁,只需要标记一下即可。这就是偏向锁的思想。对象初始化后且没有线程获取其锁后,该对象是可偏向的。当第一个线程访问它并尝试获取锁时,它会记录这个线程。如果尝试获取锁的线程是偏向锁的所有者,那么它可以直接获取锁,开销很小,性能最好。

  1. 轻型锁

JVM开发者发现,很多情况下,synchronized中的代码是由多个线程交替执行的,而不是同时执行的。也就是说,没有实际的竞争,或者只是很短的一段时间。锁竞争可以通过CAS解决。在这种情况下,没有必要使用完全互斥的重量级锁。轻量级锁是指当该锁原本是偏向锁,被其他线程访问时,说明存在竞争,偏向锁会升级为轻量级锁,线程会尝试通过自旋的方式获取锁,而不是通过其他线程来获取锁。会被卡住。

  1. 重量级锁

重量级锁是互斥锁,是利用操作系统的同步机制来实现的,所以开销比较大。当多个线程直接竞争锁且锁竞争时间较长时,轻量级锁无法满足需求,锁会膨胀为重量级锁。重量级锁会导致其他申请但无法获取锁的线程进入阻塞状态。
undefined我们举个例子,假设线程A和B都使用了悲观锁,那么当它们尝试获取同步资源时,首先必须获取锁。

整个过程是,线程A加锁并获取同步资源,线程B等待,A执行完后释放锁,CPU唤醒B获取锁,B获取锁然后执行

乐观锁

乐观锁比较乐观,认为他在操作资源时其他线程不会干涉,所以他不会锁定操作的对象,阻止其他线程接触它。同时,为了保证数据的正确性,在更新之前,我会比较在我修改数据的这段时间里,数据是否被其他线程修改过:如果没有被修改过,说明我是确实只有一个操作,那么我就可以正常修改数据了;如果我发现数据和一开始得到的不一样,说明这段时间有其他线程修改了数据,说明我晚了一步,所以我会放弃这次修改,选择上报等策略错误并重试。

乐观锁的实现一般都是使用CAS算法来实现的。我们举个例子,假设此时线程A正在使用乐观锁。那么它在操作同步资源时,就不需要提前获取锁,而是可以直接读取同步资源,在自己的线程中进行计算。

线程A判断资源是否被修改。如果不是,它会计算更新。在更新资源之前,它会检查资源是否已被修改。如果没有修改,则更新同步资源
假设此时的同步资源已经被其他线程修改更新了,线程A会发现此时的数据与最初获取的数据不一致,那么线程A就不会继续修改数据,而是会修改它根据不同的业务逻辑来选择报错或者重试。

案例
  • 悲观锁:synchronized和Lock
  • 乐观锁:使用 CAS 实现原子*
  • 大喜大悲:数据库,选择更新悲观锁,版本字段乐观锁
使用场景
  • 悲观锁:适用于并发写入较多、临界区代码复杂、竞争激烈的场景。在这种场景下,悲观锁可以避免大量无用的重复尝试等消耗。
  • 乐观锁:适用于大部分数据被读取,小部分被修改的场景。也适用于虽然读写量很大,但并发不激烈的场景。

可重入锁/不可重入锁

可重入锁是指线程当前持有锁,可以在不释放锁的情况下再次获取锁。同理,不可重入锁是指虽然线程当前持有锁,但是如果想要再次获取锁,必须先释放锁,然后再尝试再次获取锁。

共享锁/独占锁

共享锁是指同一个锁可以同时被多个线程获取,而排它锁是指这个锁只能被一个线程同时获取。我们的读写锁最好的解释了共享锁和排他锁的概念。 读写锁中的读锁是共享锁,而写锁是排他锁。读锁可以同时读,并且可以被多个线程同时持有,而写锁最多只能被一个线程同时持有。

自旋锁/非自旋锁

自旋锁的概念是,如果线程现在无法获取锁,它不会直接陷入阻塞或者释放CPU资源,而是开始使用循环,不断尝试获取锁 。这个循环过程被比喻为“自旋”,就像线程“自己旋转”一样。相反,非自旋锁的概念是没有自旋进程。如果拿不到锁,就直接放弃,或者执行其他处理逻辑,比如排队、阻塞等。

学习交流群q:513650703