当前位置: 首页 > 科技观察

图解:为什么非公平锁性能更高?

时间:2023-03-14 09:58:31 科技观察

作者|王磊来源|Java中文社区(ID:javacn666)请联系授权(微信ID:GG_Stone)在Java中,synchronized和ReentrantLock默认使用非公平锁,使用非公平锁的原因也不尽相同。它是一致的,都是为了提高程序的性能。那么非公平锁为什么能提高性能呢?接下来,我们一起来看看吧。Unfairlock不公平锁:每个线程获取锁的顺序是随机的,不遵循先到先得的规则。任何线程都可能在某个时刻直接获取并拥有锁。就像雷哥去加油,到了加油站,发现前面有人在加油,我就在车里刷了抖音。过了一会儿,前面的车加完油就走了,雷哥却没注意到,还在车里开心的刷着抖音。不过这时候又有一辆车来到了加油站,发现有空油枪,就抢在雷哥之前加满了油。这里的油枪是锁,油枪不是按照到达的先后顺序获取的,属于不公平的锁。公平锁公平锁:每个线程获取锁的顺序按照线程访问锁的顺序,总是前面的线程先获取锁。这就像在高速公路上排队过收费站一样。所有的车都要排队等候通过,第一辆车先通过收费站。性能对比公平锁和非公平锁的性能测试结果如下。以下测试数据来自《Java并发编程实战》:从上面的结果可以看出,使用非公平锁的吞吐率(单位时间内成功获取锁的平均速率)比使用公平锁高很多。性能分析上面的测试数据虽然说明了结果,但是并没有说明为什么非公平锁的性能更高呢?那么,接下来,我们通过分析公平锁和非公平执行过程来得到这个问题的答案。当公平锁执行进程获取到锁后,线程本身被加入到等待队列的尾部,进入休眠状态。当线程用完锁时,它会唤醒等待队列头部的线程尝试获取锁。锁的使用顺序是队列中的顺序,在整个过程中,线程会从运行状态切换到休眠状态,然后再从休眠状态恢复到运行状态,但是每次线程sleeps和resume,需要从用户态转换到内核态,而这种状态的转换比较慢,所以公平锁的执行速度也会比较慢。UserMode&KernelMode用户态:当一个进程正在执行用户自己的代码时,就说它处于用户运行状态。内核态:当一个任务(进程)执行系统调用,落入内核代码的执行中,我们调用内核运行态的进程。此时,处理器以最高特权级在内核代码中执行。为什么分为内核态和用户态?假设不区分内核态和用户态,程序可以随意读写硬件资源,比如随意读写和分配内存,这样如果程序员不小心写了不合适的内容到不该写的地方写入,很可能导致系统崩溃。有了用户态和内核态的区分,程序在执行某项操作时会进行一系列的验证和检查,确认没有问题后才能正常操作资源,这样就不用担心误转了当系统崩溃时,即有了内核态和用户态的区分,程序可以更安全地运行,但同时,在两种模式之间切换会造成一定的性能开销。不公平的锁执行过程当一个线程在获取锁的时候,会先尝试通过CAS获取锁。如果获取成功,则直接拥有锁。如果获取锁失败,则进入等待队列,等待下一次尝试获取锁。这样做的好处是锁的获取不需要遵循先到先得的规则,从而避免了线程休眠和回收的操作,从而加快了程序的执行效率。比如前几天雷哥去一个小营业厅办理网络转账的业务。他去了之后,发现前面有人在做生意,就对前面的小姐(经手)说:“你在门口休息一下,你等着我把事情办完,请到门口去吧。”打电话给我。”小姑娘也好心,当即答应了。但是从小姐办完业务给我打电话到我回到柜台办理业务之间有一段空闲时间,这与等待队列中的线程唤醒和等待线程之间的空闲时间相同恢复执行,而在这个空闲时间,另一个老李来到营业厅支付电话费。老李交完话费,我就回来了,可以直接办理业务了。这是一个“三赢”的局面。老李不用在我后面排队交话费,我也不用等老李交完话费再走转接流程。而且,业务员在单位时间内的业务处理效率得到了提升,她可以早点回家了。这就是所谓的“三赢”。在更短的时间内执行更多的任务,这就是非公平锁的优势。小结在本文中,我们介绍了公平锁和非公平锁的定义和执行过程。从两者的执行过程细节可以看出,由于非公平锁不需要按(顺序)顺序执行,后面的锁也可以直接尝试获取锁,没有阻塞和恢复执行的步骤,所以它的性能会更高。