并发-ReentrantLock

本文深入探讨了Java中的可重入锁ReentrantLock,包括其公平锁和非公平锁的特性。非公平锁在多轮竞争中由于允许线程重试获取锁,从而减少了上下文切换,提高了性能。公平锁则遵循先来后到原则,避免了饥饿现象。文中还提供了源码分析,展示了锁的获取和释放过程,并通过实验验证了非公平锁的性能优势。
摘要由CSDN通过智能技术生成

1. 介绍

可重入锁ReentrantLock,支持可重入的锁。表示同一个线程可对资源重复加锁。

1.1 特性

  • 支持公平锁和非公平锁,默认是非公平锁。公平锁的存在是减少“饥饿”发生的概率,等待越久的请求优先满足;

1.2 ReentrantReadWriteLock

可重入读写锁与可重入锁的区别在于原本代表同步状态的变量一分为二变成维护多个状态,高位代表读,低位代表写,通过位运算计算。大致上差不多,其它细节就不赘述了。

 

(1)锁降级 

这里的锁降级其实是线程获取读锁资源时,判断当前线程是否已经持有写锁了,而不是释放写锁再获取读锁,是处于一种共用状态。

2. 源码

2.1 线程如何重复获取锁及最终的释放(以非公平锁为例)

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    // 如果当前线程获取到锁,则更新
                    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;
}

2.2  线程如何保证锁的最终释放(以非公平锁为例)

protected final boolean tryRelease(int releases) {
            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;
}

2.3 公平锁如何实现

(1) 

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // 当前节点是否有前驱节点,有就返回true,代表有线程比当前线程更早地请求获取锁
                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;
}

(2)

  • 当队列只有一个节点或者无节点时,无需排队,返回false (第一个节点是不参与排队的,它本身是持有同步状态的)
  • 第二个节点是当前线程本身,返回false
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());
}

PS: 这里有一个地方需要注意,就是ReentrantLock设置了公平锁,调用tryLock()方法是使用的非公平锁的方式;只有调用tryLock(long timeout, TimeUnit unit)方法是用公平锁。

2.4 非公平锁为什么比公平锁性能高

无论是公平锁还是非公平锁,只要进入了等待队列,那么就是要满足FIFO原则。

但是要注意的是并不是只有头节点能获取同步状态。当非公平锁时,在同步队列中的任意线程都能获取同步状态。我们假设5个线程按照顺序进入同步队列(A、B、C、D、E),如果此时同步队列依次是A、B、C、D,那么E线程尝试获取锁时会与同步队列的头节点线程A争抢锁资源,已经在等待队列中的线程是公平竞争锁(从头节点到尾节点);

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    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;
}

从目前单轮线程运行来看,公平锁和非公平锁的并没有多大区别,但是为什么非公平锁性能比公平锁好呢?

import org.junit.Test;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class FairAndUnfairTest {
    private static Lock fairLock = new ReentrantLockMine(true);
    private static Lock unfairLock = new ReentrantLockMine(false);

    @Test
    public void unfair() throws InterruptedException {
        testLock("非公平锁", unfairLock);
    }

    @Test
    public void fair() throws InterruptedException {
        testLock("公平锁", fairLock);
    }

    private void testLock(String type, Lock lock) throws InterruptedException {
        System.out.println(type);
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Job(lock)){
                public String toString() {
                    return getName();
                }
            };
            thread.setName("" + i);
            thread.start();
        }
        Thread.sleep(11000);
    }

    private static class Job implements Runnable{
        private Lock lock;
        public Job(Lock lock) {
            this.lock = lock;
        }

        public void run() {
            for (int i = 0; i < 2; i++) {
                lock.lock();
                try {
                    Thread.sleep(1000);
                    System.out.println("获取锁的当前线程[" + Thread.currentThread().getName() + "], 同步队列中的线程" + ((ReentrantLockMine)lock).getQueuedThreads() + "");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    private static class ReentrantLockMine extends ReentrantLock {  //重新实现ReentrantLock类是为了重写getQueuedThreads方法,便于我们试验的观察
        public ReentrantLockMine(boolean fair) {
            super(fair);
        }

        @Override
        protected Collection<Thread> getQueuedThreads() {   //获取同步队列中的线程
            List<Thread> arrayList = new ArrayList<Thread>(super.getQueuedThreads());
            Collections.reverse(arrayList);
            return arrayList;
        }
    }
}

 

在多轮情况非公平锁中,线程释放锁再立马获取锁,是具备大概率获取锁的几率的。这样就减少了线程上下文切换,减少了运行时间。因此判断ReentrantLock性能是否有优化空间的标准:

  • 同一线程是否会多次获取锁资源

3. 实战

4. FAQ

5. 参考资料

【试验局】ReentrantLock中非公平锁与公平锁的性能测试

[Java多线程进阶(十一)—— J.U.C之locks框架:StampedLock]

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值