公平锁/非公平锁/可重入锁/递归锁/自旋锁

意识上传
• 阅读 260

什么是公平锁,什么是非公平锁,两者有什么区别?


公平锁和非公平锁是并发编程中的概念,用于描述线程获取锁的方式和顺序。它们的区别在于线程在竞争锁时的公平性。

  1. 公平锁(Fair Lock):公平锁是指多个线程按照申请锁的顺序获取锁,即按照线程的先后顺序来排队获取锁。当一个线程释放锁后,等待时间最长的线程会获得锁的访问权。公平锁能够保证每个线程都有机会获取到锁,避免饥饿现象的发生。
  2. 非公平锁(Unfair Lock):非公平锁是指多个线程获取锁的顺序是不确定的,不按照申请锁的顺序来排队。一个线程在等待锁时,不管自己是不是在等待队列的头部,都有机会在其他线程释放锁后立即获取锁。非公平锁允许某些线程相对于其他线程具有更高的获取锁的机会,可能会导致某些线程长时间地无法获取到锁,产生饥饿现象。

两者的区别在于线程获取锁的顺序和公平性。公平锁保证了线程按照申请锁的顺序获取锁,公平性较高,但可能会导致线程等待时间较长。非公平锁允许线程插队获取锁,提高了整体的吞吐量,但可能会导致某些线程一直无法获取到锁。

在Java中,ReentrantLock类可以作为公平锁或非公平锁来使用,通过构造函数的参数来指定。默认情况下,ReentrantLock是非公平锁。



什么是可重入锁?


可重入锁(Reentrant Lock)是一种特殊类型的锁,也称为递归锁。它允许同一个线程多次获取同一个锁,而不会造成死锁。当一个线程已经持有锁时,它可以再次获取该锁而不被阻塞,而其他线程在获取该锁时会被阻塞,直到该线程释放锁。

可重入锁的主要特点是线程可以重复获取锁,每次获取锁后,锁的持有计数会加1,每次释放锁后,锁的持有计数会减1。只有当锁的持有计数为0时,其他线程才能获取该锁。这样可以避免同一个线程在递归调用或嵌套方法中重复获取锁而造成死锁。

可重入锁的一个典型应用场景是在一个线程中调用了一个同步方法,而该同步方法又调用了同一个类中的另一个同步方法。在这种情况下,如果没有可重入锁的支持,线程在调用第二个同步方法时会被自己持有的锁阻塞,导致死锁。而可重入锁允许线程重复获取锁,避免了死锁的问题。

在Java中,ReentrantLock类就是可重入锁的一种实现。它提供了与synchronized关键字类似的功能,但更加灵活和可控。通过ReentrantLock,可以实现更复杂的线程同步和互斥操作,并提供了额外的特性,如可定时的、可中断的锁等。



什么是自旋锁?


自旋锁(Spin Lock)是一种线程同步的机制,与传统的互斥锁(如互斥量)不同,自旋锁不会将线程阻塞在等待锁的位置,而是通过循环忙等(自旋)的方式尝试获取锁,直到成功获取为止。

当一个线程尝试获取自旋锁时,如果锁已经被其他线程持有,则该线程不会被阻塞,而是会在一个循环中不断地检查锁是否被释放。这种自旋的行为可以避免线程切换的开销,因为线程不需要进入阻塞状态,而是一直处于活跃状态等待锁的释放。只有当自旋锁的持有者释放锁时,等待获取锁的线程才能成功获取锁并继续执行。

自旋锁适用于以下情况:

  • 锁被持有的时间很短,期望通过自旋等待锁的释放来避免线程切换的开销。
  • 线程在获取锁时,锁的竞争情况较为轻微,即很少发生锁的争用。
  • 系统具有多处理器或多核心,使得自旋等待期间可以让其他线程继续执行。

需要注意的是,自旋锁在多核心或多处理器系统中才能发挥较好的效果。如果系统只有一个处理器,自旋等待的线程会一直占用处理器资源,导致其他线程无法执行,这种情况下使用自旋锁反而会降低性能。

在Java中,自旋锁的一种实现是AtomicInteger类的compareAndSet()方法,它使用了硬件级别的原子操作来实现自旋等待。此外,Java 5及以上版本还引入了java.util.concurrent包中的SpinLock类,提供了更完整的自旋锁实现。


自旋锁代码示例

可以使用AtomicBoolean来实现一个简单的自旋锁。下面是一个基本的自旋锁代码示例:

import java.util.concurrent.atomic.AtomicBoolean;

public class SpinLock {
    private AtomicBoolean locked = new AtomicBoolean(false);

    public void lock() {
        while (!locked.compareAndSet(false, true)) {
            // 自旋等待锁释放
        }
    }

    public void unlock() {
        locked.set(false);
    }
}

在上面的代码中,AtomicBoolean用于表示锁的状态,locked变量初始化为false表示锁是未被占用的状态。lock()方法使用一个循环来自旋等待锁的释放,直到成功获取到锁。compareAndSet(false, true)方法会尝试将locked变量从false设置为true,如果设置成功,则表示获取到了锁。unlock()方法将锁的状态设置为false,表示锁被释放。

你可以在多线程环境下使用这个自旋锁,例如:

public class Main {
    private static SpinLock spinLock = new SpinLock();
    private static int counter = 0;

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                spinLock.lock();
                counter++;
                spinLock.unlock();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                spinLock.lock();
                counter--;
                spinLock.unlock();
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Counter: " + counter);
    }
}

在上面的示例中,我们创建了两个线程,一个线程对counter进行增加操作,另一个线程对counter进行减少操作。在每个操作之前,线程会先获取自旋锁,然后执行操作,最后释放自旋锁。通过自旋锁的使用,可以保证对counter的操作是线程安全的。

通过使用自旋锁,线程在获取锁的过程中不会进入阻塞状态,而是通过不断地自旋等待锁的释放。这可以有效地减少线程的切换开销,并提高程序的性能。

需要注意,自旋锁适用于短时间内持有锁的情况,如果锁的持有时间较长或者线程竞争激烈,自旋等待的时间会很长,影响性能。在这种情况下,可以考虑使用其他更高级的同步机制,如ReentrantLock、Semaphore等。

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
java 里面 的锁
A、乐观锁、悲观锁B、偏向锁、轻量级锁、重量级锁C、互斥锁、自旋锁、适应性自旋D、可重入锁、读写锁E、公平锁、非公平锁F、总线锁、缓存锁(linux操作系统底层,由CPU提供的锁)G、锁优化:减少锁持有时间、减小锁粒度、锁分离、锁粗化、锁消除信号量与互斥量:信号
Wesley13 Wesley13
3年前
java中的锁
记录一下公平锁,非公平锁,可重入锁(递归锁),读写锁,自旋锁的概念,以及一些和锁有关的java类。公平锁与非公平锁:公平锁就是在多线程环境下,每个线程在获取锁时,先查看这个锁维护的队列,如果队列为空或者自身就是等待队列的第一个,就占有锁。否则就加入到等待队列中,按照FIFO的顺序依次占有锁。非公平锁会一上来就试图占
Wesley13 Wesley13
3年前
java面试题汇总,不断更新中。。。
JVM,并发,锁相关:1.请你谈谈对volatile的理解,volatile是否存在伪共享问题。2.cas你知道吗?3.原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?4.公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁。5.CountDownLatch、CyclicBarrier、S
Wesley13 Wesley13
3年前
Java并发包小结
1、Lock  Lock功能对应关键字synchrozied功能,lock和unlock方法用于加锁和释放锁。等待锁的线程加入到等待链表中,同时阻塞线程,锁释放时,从等待链表中取出等待的线程执行,取等待的线程分公平与非公平两种方式,公平方式取第一个等待的线程,非公平方式当前正在获取锁的线程可能立刻执行,而不用加入到等待队列中,排队执行。2、Con
Wesley13 Wesley13
3年前
Mysql 乐观锁 和悲观锁
平时看博客或技术文章的时候,经常被各种锁搞得晕晕乎乎,包括在自旋锁、可重入锁、公平锁等等、乐观锁、悲观锁、行锁、表锁、意向锁、排它锁等。前段时间终于把Java多线程相关的锁有机会学习了一遍。现在开始整理mysql相关的锁概念。先从乐观锁和悲观锁开始聊聊。首先要知道,乐观锁和悲观锁不是真实存在的锁,只是两种抽象概念性的东西,就相当于Java中的接口,只
Stella981 Stella981
3年前
ReentrantLock源码——获取公平锁、非公平锁、释放锁
一般用ReentrantLock的方式如下://新建非公平锁实例LocklocknewReentrantLock();//新建非公平锁实例//LocklocknewReentrantLock(true);//加锁lock.lock();try{
Stella981 Stella981
3年前
AQS共享锁应用之Semaphore原理
我们调用Semaphore方法时,其实是在间接调用其内部类或AQS方法执行的。Semaphore类结构与ReetrantLock类相似,内部类Sync继承自AQS,然后其子类FairSync和NoFairSync分别实现公平锁和非公平锁的获取锁方法tryAcquireShared(intarg),而释放锁的tryReleaseShared(inta
Wesley13 Wesley13
3年前
JDK里的自旋锁
自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时才能进入临界区。JDK里面自旋锁的实现有SynchronousQueue 和LinkedTransferQueue。 本文只是自己对源码的简单理解。先说公平锁,先等待的线程先获得数据。SynchronousQueue的内部类TransferQueue实现了公平锁。
Wesley13 Wesley13
3年前
Java中所有锁介绍
在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类。介绍的内容如下:1.公平锁/非公平锁2.可重入锁/不可重入锁3.独享锁/共享锁4.互斥锁/读写锁5.乐观锁/悲观锁6.分段锁7.偏向锁/轻量级锁/重量级锁8.自旋锁上面是很多锁的名词,这些分类并不是全是指锁的
Wesley13 Wesley13
3年前
Java 并发编程:AQS 的公平性
所谓公平是指所有线程对临界资源申请访问权限的成功率都一样,它不会让某些线程拥有优先权。通过几篇文章的分析我们知道了JDK的AQS的锁是基于CLH锁进行优化的,而其中使用了FIFO队列,也就是说等待队列是一个先进先出的队列。那是否就可以说每条线程获取锁时就是公平的呢?关于公平性,严格来说应该分成三个点来看:入队阶段、唤醒阶段以及闯入策略。友情链接: