后端—Java中的公平锁和非公平锁实现详解

  • Post author:
  • Post category:java



Java中的公平锁和非公平锁实现详解

在Java中实现锁的方式有两种,一种是使用Java自带的关键字

synchronized

对相应的类或者方法以及代码块进行加锁,另一种是

ReentrantLock

,前者只能是非公平锁,而后者是默认非公平但可实现公平的一把锁。




ReentrantLock



的实现是基于其内部类



FairSync

(公平锁)






NonFairSync

(非公平锁)


实现的。 其可重入性是基于



Thread.currentThread()



实现的: 如果当前线程已经获得了执行序列中的锁, 那执行序列之后的所有方法都可以获得这个锁。

在本文将集中对ReentrantLock以及它实现公平锁和非公平锁的方式进行讲解:

首先我们来看一段ReentrantLock的源代码,有助于我们等会对非公平和公平锁的理解:

public class ReentrantLockExample {
	int a=0;
	ReentrantLock lock= new ReentrantLock();
	public void write() {
		lock.lock();                // 获取锁
		try {
			a++;
		}finally  {
			lock.unlock();          // 释放锁
		}
	}
	
	public void reader() {
		lock.lock();                // 获取锁
		try {
			int i=a;
			...
		} finally {
			lock.unlock();          // 释放锁
		}
	}
}

ReentrantLock的实现依赖于Java同步器框架AbstractQueuedSynchronizer(AQS)。AQS使用一个整形的volatile变量state来维护同步状态,这个volatile变量是实现ReentrantLock的关键。我再看一下下面ReentrantLock的类图


公平锁:

公平和非公平锁的队列都基于锁内部维护的一个双向链表,表结点Node的值就是每一个请求当前锁的线程。公平锁则在于每次都是依次从队首取值。

锁的实现方式是基于如下几点:

表结点Node和状态

state

的volatile关键字。

sum.misc.Unsafe.compareAndSet的原子操作。


非公平锁:

在等待锁的过程中, 如果有任意新的线程妄图获取锁,都是有很大的几率直接获取到锁的。

ReentrantLock锁都不会使得线程中断,除非开发者自己设置了中断位。

ReentrantLock获取锁里面有看似自旋的代码,但是它不是自旋锁。

ReentrantLock公平与非公平锁都是属于排它锁。

ReentrantLock的可重入性分析

这里有一篇对锁介绍甚为详细的文章

朱小厮的博客-Java中的锁

.



synchronized的可重入性

参考这篇文章:

Java内置锁synchronized的可重入性

不过考虑到基础知识不一,如果对锁的概念不太清晰的也可参考我这篇博客

https://blog.csdn.net/weixin_42504145/article/details/84995202



jvm—静态方法加锁和非静态方法加锁的区别

ReentrantLock的可重入性

前言里面提到,ReentrantLock重入性是基于Thread.currentThread()实现的: 如果当前线程已经获得了锁, 那该线程下的所有方法都可以获得这个锁。ReentrantLock的锁依赖只有 NonfairSync和FairSync两个实现类, 他们的锁获取方式大同小异。

可重入性的实现基于下面代码片段的



else if



语句)(如果想要真正理解ReentrantLock的课重入性要仔细理解上面那片博客,以及下面给出的else if 语句)

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();         // 获取锁的开始,首先读volatile变量
    if (c == 0) {               // 这个if条件是没有其他线程获取该对象的锁,我们用CAS尝试获取锁
       if(ifFirst(current) && compareAndSetState(0,acquires)){
            setExclusiveOwnerThread(current);
            return(true)        // 这个方法在cas成功后,将对象的Mark Word里的锁标志改成该线程
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        // 是当前线程,直接获取到锁。实现可重入性。
        int nextc = c + acquires;    // acquires默认传入1,相当于让nextc自增
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);             // 将标志位改成自增后的值
        return true;
    }
    return false;
}

此处有两个值需要关心:

/**

*


The current owner of exclusive mode synchronization.



* 持有该锁的当前线程

*/

private transient Thread exclusiveOwnerThread;

—————–两个值不在同一个类—————-

/**



* The synchronization state.



* 0: 初始状态-无任何线程得到了锁

* > 0: 被线程持有, 具体值表示被当前线程持有的执行次数

*

* 这个字段在解锁的时候也需要用到。

* 注意这个字段的修饰词:


volatile



*/

private volatile int state;

公平锁FairSync

公平锁的实现机理在于每次有线程来抢占锁的时候,都会检查一遍有没有等待队列,如果有, 当前线程会执行如下步骤:

if (!hasQueuedPredecessors() &&

compareAndSetState(0, acquires))

{ setExclusiveOwnerThread(current);

return true; }

其中


hasQueuedPredecessors


是用于检查是否有等待队列的。

public final boolean hasQueuedPredecessors() {


Node t = tail; // Read fields in reverse initialization order

Node h = head;

Node s;

return h != t &&

((s = h.next) == null || s.thread != Thread.currentThread());

}

非公平锁NonfairSync

非公平锁在实现的时候多次强调随机抢占:

if (c == 0) {

if (compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true; }

}

与公平锁的区别在于新晋获取锁的进程会有多次机会去抢占锁。如果被加入了等待队列后则跟公平锁没有区别。

ReentrantLock锁的释放

ReentrantLock锁的释放是逐级释放的,也就是说在 可重入性 场景中,必须要等到场景内所有的加锁的方法都释放了锁, 当前线程持有的锁才会被释放!

释放的方式很简单, state字段减一即可:

protected final boolean tryRelease(int releases) {


//  releases = 1

int c = getState() – releases;

if (Thread.currentThread() != getExclusiveOwnerThread())

throw new IllegalMonitorStateException();

boolean free = false;

if (c == 0) {


free = true;

setExclusiveOwnerThread(null);

}

setState(c);

return free;

}



版权声明:本文为weixin_42504145原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。