Juc并发编程07——公平锁真的公平吗(源码剖析)

简介: 先来回顾下公平锁的tryAcquire代码

先来回顾下公平锁的tryAcquire代码。

    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出现误判会怎么样呢?公平锁是不是就不公平了呀。那我们来研究下hasQueuedPredecessors,是不是真有百密一疏的情况。


假如线程1已经持有锁了。这个该时候线程2来获取锁,走到hasQueuedPredecessors()返回false,接着往后面执行CAS,肯定执行失败,因为现在锁被线程1占有,返回aquire方法

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

接着走就是走到addWaiterenq方法。线程2进入等待队列

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // 此时等head、tail为空,满足`t==null`的条件。
                if (compareAndSetHead(new Node())) //这里没有其它线程争抢,成功
                    tail = head; // 设置成功
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

假设线程2走到注释截至处,现在有一个线程3来抢锁。当它判断等待队列时hasQueuedPredecessors会返回false,因为h==t

    public final boolean hasQueuedPredecessors() {
        Node t = tail; 
        Node h = head;
        Node s;
        return h != t && //不满足条件
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

那么问题来了,实际上等待队列中应该有线程2,现在我们却得出了等待队列为空的判断呀,线程3不的直接走CAS吗,万一线程1刚好释放锁了,线程3就插队了阿。由此可见,公平锁并不公平。

     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是否会出现误判。

相关文章
|
4月前
|
Java
JUC并发编程之等待唤醒机制
在JUC(Java Util Concurrent)并发编程中,线程等待唤醒机制是实现线程之间协作和同步的重要手段。这种机制允许一个线程挂起等待某个条件满足后被唤醒,以及另一个线程在满足某个条件后唤醒等待的线程。在Java中,有多种实现线程等待唤醒机制的方式,包括使用Object的wait()和notify()方法、Condition接口以及LockSupport类。
|
1月前
|
Java
Java并发-如何避免死锁
一般在Java项目里用到锁的场景不多,有朋友调侃说用到锁的次数还没有面试被问到的次数多,哈哈!
Java并发-如何避免死锁
|
8月前
|
Java
并发编程——ReentrantLock
Java中提供锁,一般就是synchronized和lock锁,ReentrantLock跟synchronized一样都是互斥锁。如果竞争比较激烈,推荐lock锁,效率更高。如果几乎没有竞争,推荐synchronized。
17 0
【JUC】NonfairSync 非公平实现
【1月更文挑战第15天】【JUC】NonfairSync 非公平实现
|
8月前
|
Java
多线程和并发编程(3)—AQS和ReentrantLock实现的互斥锁
多线程和并发编程(3)—AQS和ReentrantLock实现的互斥锁
53 0
|
9月前
|
缓存 监控 安全
JUC并发编程之线程锁(一)
1.ReentrantLock(互斥锁)、2.ReentRantReaderWriterLock(互斥读写锁)、3.StampedLock(无障碍锁)、4.Condition(自定义锁)、5.LockSupport
57 0
|
Java
并发编程(六)ReentrantLock
并发编程(六)ReentrantLock
89 0
|
SpringCloudAlibaba 前端开发 Java
JUC系列(二)八个有关锁的问题,让你吃透各种场景
8锁就是关于的锁的八个问题,下面也有四个demo类来阐述各种场景下锁的不同状态
JUC系列(二)八个有关锁的问题,让你吃透各种场景
juc并发编程05——读写锁
可重入锁是一种排他锁,同一时间只允许一个线程操作竞争资源。读写锁是针对读、写场景设计的,允许多个线程同时持有锁。读写锁维护了一个读锁和一个写锁。其机制如下:
|
测试技术 API
Juc并发编程04——可重入锁、公平锁与非公平锁
1.ReentrantLock使用介绍 之前我们一直使用的Lock实例都用的是ReentrantLock,实际上,这是一种可重入锁。简单来说,就是对同一个线程可以进行多次的加锁操作