一个线程启动时刚好碰到另外的线程释放锁,则该线程会获取到锁,其他等待队列中的线程不会获取到锁。好处:减少线程状态切换(不用在start()之后进入阻塞),提高吞吐量。
非公平锁
非公平锁是多个线程加锁时直接尝试获取锁,能抢到锁到直接占有锁,抢不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU 不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
公平锁概念
讲解公平锁会涉及到一些理论性的概念,先在这里做个说明。
1.AQS--指AbstractQueuedSynchronizer类
AQS是java中管理锁的抽象类,锁的很多公共方法都是在AQS里面实现的。AQS是独占锁和共享锁的公共父类。
2.AQS 锁的类别
独占锁--锁在同一时间点只能被一个线程持有。根据锁的获取机制,又分为公平锁和非公平锁。公平锁就是根据CLH等待队列FIFO的规则,先来先获取锁。非公平锁就是在线程要获取锁时,无视CLH队列,直接获取锁。独占锁的典型例子就是ReentrantLock,ReentrantReadWriteLock.WriteLock也是独占锁。
共享锁--锁在同一时间点可以被多个线程同时共享,ReentrantReadWriteLock.ReadLock,CountDownLatch,Semaphore,CyclicBarrier都是共享锁。
3.CLH队列
CLH队列是AQS线程等待的队列。在独占锁中,当资源被一个线程占有时,其他线程就需要等待,这时CLH就是用来管理这些等待线程的队列。
CLH队列是非阻塞FIFO队列,也就是说往队列添加或移除节点时,并发情况下不会出现阻塞,是通过自旋锁和CAS来保证添加或移除的原子性。
4.CAS
这个不用多说了,CompareAndSwap,原子操作函数。原理在另外一篇文章中有写到。
ReentrantLock数据结构
重入锁的数据结果借图来看一下
Reentrant = Re + entrant,Re是重复、又、再的意思,entrant是enter的名词或者形容词形式,翻译为进入者或者可进入的,所以Reentrant翻译为可重复进入的、可再次进入的,因此ReentrantLock翻译为重入锁或者再入锁。
重入锁实现了Lock接口,ReentrantLock包含了Sync对象,Sync是由AQS的子类,Sync还有两个子类FairSync和NonfairSync。所以是否是公平锁取决于使用的Sync对象。
可以看下ReentrantLock的构造方法,默认使用的非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
知识来源:
【并发与线程】你对“公平锁”了解吗?为什么会有“非公平锁”?_哔哩哔哩_bilibili
公平锁和非公平锁_这个名字先用着的博客-CSDN博客
https://www.cnblogs.com/dpains/p/7495633.html
公平锁和非公平锁_这个名字先用着的博客-CSDN博客