Java并发笔记 (10)---- ReentrantLock

重入锁(ReentrantLock)

就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。除此之外,该锁的还支持获取锁时的公平和非公平性选择。

ReentrantLock虽然没能像synchronized关键字一样支持隐式的重进入,但是在调用lock()方
法时,已经获取到锁的线程,能够再次调用 lock() 方法获取锁而不被阻塞。

下面将着重分析ReentrantLock是如何实现重进入和公平性获取锁的特性!

1. 实现重新进入

该特性的实现需要解决以下两个问题:

  • 线程再次获取锁:锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
  • 锁的最终释放:线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。

非公平性(默认的)实现为例,源代码如下:

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;
}

成功获取锁的线程再次获取锁,只是增加了同步状态值,这也就要求ReentrantLock在释放同步状态时减少同步状态值,tryRelease(int releases) 源码如下:

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;
}

如果该锁被获取了n次,那么前**(n-1)tryRelease(int releases)方法必须返回false**,而只有同
步状态完全释放了,才能返回true

2. 公平与非公平获取锁的区别

如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO

上面的代码介绍了非公平锁的tryAcquire(int acquires),下面介绍一下公平锁的该方法。

下面献上源码

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;
}

该方法与nonfairTryAcquire(int acquires)比较,唯一不同的位置为判断条件多hasQueuedPredecessors() 方法,如果该方法返回为true,则表示有线程更早的获取了锁,所以无法获取同步状态。

下面附上一个例子来说明这两种锁:

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

    public static void main(String[] args) {
        FairAndUnfairTest test = new FairAndUnfairTest();
        test.fair();
    }
    public void fair() {
        testLock(fairLock);
    }
    public void unfair() {
        testLock(unfairLock);
    }
    private void testLock(Lock lock) {
// 启动5个Job(略)
        for (int i = 0; i < 5; i++) {

            new Job(lock).start();

        }
    }
    private static class Job extends Thread {
        private Lock lock;
        public Job(Lock lock) {
            this.lock = lock;
        }
        public void run() {
// 连续2次打印当前的Thread和等待队列中的Thread(略)

            for (int i = 0; i < 2; i++) {
                lock.lock();
                System.out.println(Thread.currentThread().getName()+ " waiting: " + ((ReentrantLock2)lock).getQueuedThreads());
                lock.unlock();
            }

        }
    }
    private static class ReentrantLock2 extends ReentrantLock {
        public ReentrantLock2(boolean fair) {
            super(fair);
        }
        public Collection<Thread> getQueuedThreads() {
            List<Thread> arrayList = new ArrayList<Thread>(super.
                    getQueuedThreads());
            Collections.reverse(arrayList);
            return arrayList;
        }
    }
}

输出:

Thread-0 waiting: []
Thread-2 waiting: [Thread[Thread-1,5,main], Thread[Thread-3,5,main], Thread[Thread-4,5,main], Thread[Thread-0,5,main]]
Thread-1 waiting: [Thread[Thread-3,5,main], Thread[Thread-4,5,main], Thread[Thread-0,5,main], Thread[Thread-2,5,main]]
Thread-3 waiting: [Thread[Thread-4,5,main], Thread[Thread-0,5,main], Thread[Thread-2,5,main], Thread[Thread-1,5,main]]
Thread-4 waiting: [Thread[Thread-0,5,main], Thread[Thread-2,5,main], Thread[Thread-1,5,main], Thread[Thread-3,5,main]]
Thread-0 waiting: [Thread[Thread-2,5,main], Thread[Thread-1,5,main], Thread[Thread-3,5,main], Thread[Thread-4,5,main]]
Thread-2 waiting: [Thread[Thread-1,5,main], Thread[Thread-3,5,main], Thread[Thread-4,5,main]]
Thread-1 waiting: [Thread[Thread-3,5,main], Thread[Thread-4,5,main]]
Thread-3 waiting: [Thread[Thread-4,5,main]]
Thread-4 waiting: []

Process finished with exit code 0

上面只run了公平锁的程序,可以看到下一个执行的线程与同步队列中的线程顺序有关。

非公平性锁出现了一个线程连续获取锁的情况(此处需要读者修改上述代码进行验证!)

非公平性锁可能使线程“饥饿”,为什么它又被设定成默认的实现呢?

我们通过下面的表来分析一下!

在这里插入图片描述

在测试中公平性锁与非公平性锁相比,总耗时是其94.3倍,总切换次数是其133倍。可以看出,公平性锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平性锁虽然可能造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值