和朋友聊天,他提到:ReentrantLock的构造函数可以传一个bool数据,为true时构造一个“公平锁”,为false时构造一个“非公平锁”锁”。印象中锁是不区分类型的,所以我觉得这应该是Java发明的一个概念,所以就补上了。锁的底层实现不管是什么语言,在操作系统层面对锁的操作都会变成系统调用(SystemCall),以Linux为例,就是futex函数,可以理解为两个函数:futex_wait(s),为变量s加锁;futex_wake(s)释放s上的锁,唤醒其他线程。如果熟悉操作系统的原理,其实就是P/V操作。Java公平锁和非公平锁公平锁的加锁操作是调用futex_wait,解锁操作是调用futex_wake。比如下面代码中非公平锁的加锁/解锁操作,会先进行一次CAS操作,然后调用futex_wait和futex_wake。例如,下面的代码在锁定之前添加了一个CAS原子操作。它接受三个变量,可以理解为如下逻辑:如果第一个参数的值不等于第二个参数,则返回0,否则表示操作失败。用新值更新。这个功能不是通过代码实现的,而是CPU提供的一条指令,比如Intel的cmpxchg;它被封装在高级语言中,比如Java的Atomic变量。为什么你会问为什么理解了原理之后,在加锁之前通过CAS修改一个变量来表示“我要加锁”。这似乎是一个非常多余的操作。事实上,这是一个旋转。如果资源用得很快,可以改进。系统吞吐量。考虑如下场景,加锁前时间为t1,加锁后时间为t2(使用资源),释放锁时间为t3。现在有两个线程,处于t1状态,其中A线程先抢资源,处于t2状态;B线程也会尝试加锁,同时t2被释放,加锁动作执行成功。B被停职;系统继续执行A释放成功唤醒B继续执行。上述过程中,只要B再次等待“一丢丢”,就不需要暂停,直接获取资源继续执行。非公平锁的CAS操作就是增加时间。使用非公平锁,如果系统中有3个线程在执行,A抢到了资源,而C却处于pending状态。此时B尝试CAS操作,A刚释放资源,还没来得及唤醒C,则B先抢到资源,先于C执行,这就是“不公平”的由来。C虽然老老实实等了很久,但B很好地把握了“时机”,迅速“插队”完成了资源抢占。综上所述,加锁过程本身也是有时间开销的。如果操作资源的时间比加锁时间短,建议使用非公平锁来提高系统的吞吐量;否则,诚实地使用公平锁。【本文为专栏作家“行森”原创文章,转载请联系作者获得授权】点此阅读更多本作者好文
