关于java:面试突击46公平锁和非公平锁有什么区别

23次阅读

共计 2300 个字符,预计需要花费 6 分钟才能阅读完成。

从偏心的角度来说,Java 中的锁总共可分为两类:偏心锁和非偏心锁。但偏心锁和非偏心锁有哪些区别?孰优孰劣呢?在 Java 中的利用场景又有哪些呢?接下来咱们一起来看。

注释

偏心锁:每个线程获取锁的程序是依照线程拜访锁的先后顺序获取的,最后面的线程总是最先获取到锁。
非偏心锁:每个线程获取锁的程序是随机的,并不会遵循先来先得的规定,所有线程会竞争获取锁。
举个例子,偏心锁就像开车通过收费站一样,所有的车都会排队期待通过,先来的车先通过,如下图所示:

通过收费站的程序也是先来先到,别离是张三、李四、王五,这种状况就是偏心锁。
而非偏心锁相当于,来了一个强行加塞的老司机,它不会准守排队规定,来了之后就会试图强行加塞,如果加塞胜利就顺利通过,当然也有可能加塞失败,如果失败就乖乖去前面排队,这种状况就是非偏心锁。

利用场景

在 Java 语言中,锁 synchronized 和 ReentrantLock 默认都是非偏心锁,当然咱们在创立 ReentrantLock 时,能够手动指定其为偏心锁,但 synchronized 只能为非偏心锁。
ReentrantLock 默认为非偏心锁能够在它的源码实现中失去验证,如下源码所示:

当应用 new ReentrantLock(true) 时,能够创立偏心锁,如下源码所示:

偏心和非偏心锁代码演示

接下来咱们应用 ReentrantLock 来演示一下偏心锁和非偏心锁的执行差别,首先定义一个偏心锁,开启 3 个线程,每个线程执行两次加锁和开释锁并打印线程名的操作,如下代码所示:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockFairTest {static Lock lock = new ReentrantLock(true);
    public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 3; i++) {new Thread(() -> {for (int j = 0; j < 2; j++) {lock.lock();
                    System.out.println("以后线程:" + Thread.currentThread()
                            .getName());
                    lock.unlock();}
            }).start();}
    }
}

以上程序的执行后果如下图所示:

接下来咱们应用非偏心锁来执行下面的代码,具体实现如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockFairTest {static Lock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 3; i++) {new Thread(() -> {for (int j = 0; j < 2; j++) {lock.lock();
                    System.out.println("以后线程:" + Thread.currentThread()
                            .getName());
                    lock.unlock();}
            }).start();}
    }
}

以上程序的执行后果如下图所示:

从上述后果能够看出,应用偏心锁线程获取锁的程序是:A -> B -> C -> A -> B -> C,也就是按程序获取锁。而非偏心锁,获取锁的程序是 A -> A -> B -> B -> C -> C,起因是所有线程都争抢锁时,因为以后执行线程处于沉闷状态,其余线程属于期待状态(还须要被唤醒),所以以后线程总是会先获取到锁,所以最终获取锁的程序是:A -> A -> B -> B -> C -> C。

执行流程剖析

偏心锁执行流程

获取锁时,先将线程本人增加到期待队列的队尾并休眠,当某线程用完锁之后,会去唤醒期待队列中队首的线程尝试去获取锁,锁的应用程序也就是队列中的先后顺序,在整个过程中,线程会从运行状态切换到休眠状态,再从休眠状态复原成运行状态,但线程每次休眠和复原都须要从用户态转换成内核态,而这个状态的转换是比较慢的,所以偏心锁的执行速度会比较慢。

非偏心锁执行流程

当线程获取锁时,会先通过 CAS 尝试获取锁,如果获取胜利就间接领有锁,如果获取锁失败才会进入期待队列,期待下次尝试获取锁。这样做的益处是,获取锁不必遵循先到先得的规定,从而防止了线程休眠和复原的操作,这样就减速了程序的执行效率。
偏心锁和非偏心锁的性能测试后果如下,以下测试数据来自于《Java 并发编程实战》:


从上述后果能够看出,应用非偏心锁的吞吐率(单位工夫内胜利获取锁的均匀速率)要比偏心锁高很多。

优缺点剖析

偏心锁的长处是按序平均分配锁资源,不会呈现线程饿死的状况,它的毛病是按序唤醒线程的开销大,执行性能不高。
非偏心锁的长处是执行效率高,谁先获取到锁,锁就属于谁,不会“按资排辈”以及程序唤醒,但毛病是资源分配随机性强,可能会呈现线程饿死的状况。

总结

在 Java 语言中,锁的默认实现都是非偏心锁,起因是非偏心锁的效率更高,应用 ReentrantLock 能够手动指定其为偏心锁。非偏心锁重视的是性能,而偏心锁重视的是锁资源的平均分配,所以咱们要抉择适合的场景来利用二者。

是非审之于己,毁誉听之于人,得失安之于数。

公众号:Java 面试真题解析

面试合集:https://gitee.com/mydb/interview

正文完
 0