zoukankan      html  css  js  c++  java
  • ReentrantLock中的公平锁与非公平锁

    简介

    ReentrantLock是一种可重入锁,可以等同于synchronized的使用,但是比synchronized更加的强大、灵活。
    一个可重入的排他锁,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有。当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。如果当前线程已经拥有该锁定,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。
    

    内部实现

    ReentrantLock内部拥有一个Sync内部类,该内部类继承自AQS,该内部类有两个子类FairSync和NonfairSync,分别代表了公平锁和非公平锁,ReentrantLock默认使用非公平锁
    

    那么ReentrantLock内部的公平锁和非公平锁有什么区别呢?

    区别主要在于两种锁实现的获取锁的方式,tryAcquire方法实现的不同:
    
    首先来看看非公平锁:
    protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
    }
    final boolean nonfairTryAcquire(int acquires) {
    	final Thread current = Thread.currentThread();
    	int c = getState();//获取state
    	if (c == 0) {//state == 0表示锁没有被任何线程持有
    		if (compareAndSetState(0, acquires)) {//尝试设置state,如果设置成功,则获取到锁
    			setExclusiveOwnerThread(current);// 将当前锁的持有者设置为当前线程
    			return true;
    		}
    	}
    	else if (current == getExclusiveOwnerThread()) {//如果当前线程是锁的持有者,直接返回成功
    		int nextc = c + acquires;
    		if (nextc < 0) // overflow
    			throw new Error("Maximum lock count exceeded");
    		setState(nextc);
    		return true;
    	}
    	return false;//否则,获取锁失败
    }
    可以看到,非公平锁获取锁的过程是:首先判断当前锁是否被其他线程持有,如果是,直接返回失败,否则尝试获取锁
    
    再来看看公平锁的加锁过程:
    protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
    		compareAndSetState(0, acquires)) {
    		setExclusiveOwnerThread(current);
    		return true;
    	}
        }
        else if (current == getExclusiveOwnerThread()) {
    	    int nextc = c + acquires;
    	    if (nextc < 0)
    		    throw new Error("Maximum lock count exceeded");
    	    setState(nextc);
    	    return true;
        }
        return false;
    }
    
    对比上面的非公平锁的加锁过程,可以看到公平锁多了一个hasQueuedPredecessors方法的判断,来看看该方法的实现:
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        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());
    }
    
    其实就是判断当前线程是否为CLH队列的头节点
    
    我们知道,尝试获取锁失败的线程都会被放入到CLH队列中,然后自旋尝试获取锁。对比公平锁和非公平锁的获取方式可以看到,公平锁之所以公平,是因为后续的线程必须进入到CLH同步队列中排队等候获取锁,但是分公平锁不需要,如果某个线程尝试获取锁的时候当前锁刚好被释放掉,那么它可以直接尝试获取锁,如果获取锁成功,直接执行,获取失败时,才进入到CLH同步队列
    

    总结

    通过上面的分析可以看到,公平锁是将所有线程依次放入CLH同步队列,然后再从队列中依次取出来执行;而非公平锁是部分已经进入同步队列的线程会像公平锁一样获取锁,但是其他尚且没有进入同步队列的线程可以与CLH同步队列中的首节点线程竞争锁;这也是为什么非公平锁性能比公平锁性能更佳的原因
  • 相关阅读:
    RocketMQ性能压测分析(转载)
    利用Fiddler或Charles进行mock数据
    Linux中buffer/cache,swap,虚拟内存和page ++
    AVA 8 :从永久区(PermGen)到元空间(Metaspace)
    jstat 监控调整GC很好用
    Jmeter常用函数
    关于Oracle新建表空间,添加用户及新建表数据
    关于Oracle增加表空间大小方法
    关于手动删除Oracle数据数据,导致Oracle无法连接处理过程
    解决jquery easyui combotree(下拉树)点击文字无法展开下级菜单的解决方法
  • 原文地址:https://www.cnblogs.com/canmeng-cn/p/9410863.html
Copyright © 2011-2022 走看看