Java并发编程学习:公平锁与非公平锁的深入理解及比较
Java并发编程锁中独占公平锁与非公平锁的比较

对公平锁和非公平锁的理解:
在上一篇文章中,我们了解了非公平锁。其实Java中还是有公平锁的。怎么理解公平这个词呢?和我们实际的理解是一样的。大家按照先到先得的原则排队。在队列中,无论地位高低,每个人都受到平等对待。这就是我们现实生活中的公平。每个人都喜欢公平。但Java中的默认是不公平的,为什么呢?
本文主要内容:公平锁的现实理解;公平锁演示; Java中为什么默认是非公平锁(公平锁与非公平锁的比较)
本文是《Kage Java(Kage Java)并发编程学习》系列《锁系列》教程的第四篇:《Java并发包锁学习第五篇:了解公平锁以及与非公平锁的比较》。
现实生活中的例子:
去ATM机取款的例子也是如此。假设有 3 个人使用 ATM 取款。路人A不会使用ATM机。他自己花了5分钟才搞清楚,然后他终于学会了如何使用,但他忘记了密码。给家人打电话咨询需要1分钟。当路人A完成操作后,接下来的两个人排队继续按顺序操作。这样,谁先来,谁就先操作。手术完成后,下一个人就可以手术,无论他是富有还是贫穷。不管你是高还是低,不管你拿100还是1W,拿1W的人都要在拿100的人后面排队。这看起来很公平。无论地位高低,大家都按顺序运作。这种操作方式脱颖而出,从多线程并发的角度来看,是一种公平锁操作。
通行者A总共用了6分钟后,通行者B和通行者D又用了3分钟进行操作。即三个人总共花费的时间为9分钟。为什么会出现这种情况?因为路人都被挡住了。一直占用锁资源。当路人A在操作时,其他人只能排队等候。如果路人A不会操作ATM,后面的路人C就会插队询问路人A。他可以先插队操作ATM,同时教路人A。如果A同意,那么C就可以完成操作,A可以向别人学习操作。有可能路人A也能在2分钟内完成操作。此时,三个人总共花费的时间是5分钟;如果A不同意C的插队操作,那么C只能回到原来的位置排队等待。如果从多线程并发的角度考虑这种运行模式,路人A在模式下以及与家人交谈所花费的时间似乎就是CPU切换上下文所花费的时间。路人C插队获取ATM资源。这种操作可以看作是不公平的,因为C的进入时间晚于路人B,但C却先操作。但从最后三个人总共花费的时间来看,路人C插队了,这无疑提高了效率。这种操作在Java并发中称为非公平锁。
需要注意的是,显式锁和隐式锁默认都是不公平的。因为不公平可以提高系统的吞吐量。
非公平锁的定义:
线程首先尝试获取同步状态操作(C首先尝试跳入队列)。如果获得,则对共享变量进行操作(A同意C的队列中断,C操作ATM机)。如果无法获取,则继续排队。
使用方法二:独家公示
要求:控制台打印的结果与线程顺序一致。代码如下:
此时的代码和之前的代码唯一的区别就是如上图所示:实例化锁的时候有一个参数,设置为true。
运行结果:
从运行结果来看,我们发现控制台打印的获取锁的顺序和调用锁的顺序是一样的,达到了我们预期的结果。然而,一次仍然只有一个线程操作。当该线程操作完成释放锁后,其他线程就可以获取锁了。
公平锁和非公平锁的比较
问题:
为什么并发大师 Doug Lea 将 ReentrantLock 作为默认设计不公平?
其实要回答这个问题,我们需要比较一下公平锁和非公平锁的区别。我们首先看一下公平锁和非公平锁场景下ATM机的运行情况,如下图:
Fair lock:大家都排队。如果一个线程被阻塞(乘客A),其他线程只能等待这一情况。最终三个线程操作完成,总共耗时9分钟。
非公平锁的情况:当多个线程操作共享资源时,发现共享资源没有被锁定(乘客A还在探索进程),于是尝试插队(乘客C尝试与A通信) ,先插队并教导A),如果插队成功(A同意),则操作共享资源(C先操作ATM机);如果跳队失败(A不同意),则排队(C返回队列)。如果插队成功,最终耗时为:5分钟。
由此我们可以看出公平锁和非公平锁的优缺点。
优缺点对比: 不公平锁:
优点:效率高;缺点:容易造成线程“饥饿”。当多个线程使用不公平的方法时,有可能一个线程永远无法获得竞争的权利,导致这个线程“饿死”。
适用场景:
如果不以TPS(单位时间成功完成次数)作为唯一考虑指标,可以使用非公平锁,因为非公平锁可以提高系统的吞吐量;
公平锁:
优点:避免线程“饥饿”;缺点:性能比公平锁差很多。
欢迎聊天