多线程基础学习十三:ReentrantLock的了解与使用

最近较忙,趁晚上继续之前的学习,前面了解了Lock与AbstractQueuedSynchronizer(AQS),也了解了并发的一些重要的概念,今天就学习一下最常用的一个实现类ReentrantLock。

ReentrantLock了解

源码:

public class ReentrantLock implements Lock, java.io.Serializable {....}

从这可以看出来,ReentrantLock实现了Lock接口,而前面了解Lock的时候知道,Lock接口提供了获取锁与释放锁的方法,所以作为实现类,ReentrantLock一定也支持这些功能。

Reentrant 这个单词是折返的意思,也就是重入,上一篇学习到重入锁的概念,所以ReentranLock一定实现了重入锁。。

再简单看一下实现:

实现

从截图上可以看到,ReentranLock使用了AQS代理实现了锁的操作,而且不止一个实现,NonfairSync与FairSync,可以明显看出这是非公平同步器和公平同步器,也体现了前一篇学习到的公平锁与非公平锁。

基于以上理解,
1、可以知道锁的操作都是被同步器实现了,应该也提供了一些常用的方法;
2、实现了公平锁、非公平锁、重入锁;

ReentranLock的公平锁与非公平锁

选择使用公平锁或者非公平锁:

/**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

ReentrantLock提供了一个构造方法,通过参数true或则false决定使用公平锁还是非公平锁。

公平锁实现:
源码:

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }
        ....
 }       

获取锁的时候会调用AQS的acquire(1)方法
AQS的acquire(int)实现:

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

在这个实现中会首先调用tryAcquire(int)方法,这个方法被FairSync 重写了
FairSync 重写的tryAcquire(int):

/**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        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()方法,看看这个方法是做什么的;
这个方法是AQS里面的方法:

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

注释上说是用来帮助实现公平锁的方法,在实现返回值是一个判断结果,表达式包含三个小表达式:
– h != t
– (s = h.next) == null
– s.thread != Thread.currentThread()
h是头部元素, tail是尾部元素 s是头元素的下一个元素 ;
– 如果 h == t 说明只有一个元素 h != t 返回false,否则返回true;如果有多个元素 h != t 返回true;
– 如果只有一个元素 (s = h.next) == null ,返回true;如果有多个元素, 返回false;
– 如果是当前线程,s.thread != Thread.currentThread(), 返回false,否则返回true;

根据上面的分析,有一下结果:

返回结果是当前线程不是当前线程
只有一个元素falsefalse
多个元素falsetrue

而实现中判断是:

if (!hasQueuedPredecessors() && compareAndSetState(0, acquires))

为了使!hasQueuedPredecessors()判断通过,就必须满足两个情况:
– 只有一个元素
– 有多个元素但是线程是当前的线程(这个不太明白)或者是头元素

这就保证了一定是头元素获取到锁,这也就保证了逻辑上的公平。

ps:

同步器内部等待线程都在阻塞队列中,队列是有序的,最先进入队列的在最前面,后来进入队列的后面,类似与排队,先排队的先处理。

再来对比一下非公平锁的实现:

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的使用

用法:

public class LockTest {


    public static void main(String[] args) {

        int i = 0;
        while (i < 20) {
            new Thread(new TestRunnable()).start();
            i++;
        }
    }

    static class Test {

        static ReentrantLock lock = new ReentrantLock(false);

        public static int total = 0;

        public static void add() {
            lock.lock();
            try {
                total++;
                System.out.println(total);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }


    static class TestRunnable implements Runnable {

        public void run() {
            Test.add();
        }
    }
}
public class LockTest2 {

    static ReentrantLock lock = new ReentrantLock(false);


    public static void main(String[] args) {

        Test test = new Test();
        int i = 0;
        while (i < 20) { // 加20次
            new Thread(new AddRunnable(test)).start();
            i++;
        }
    }

    static class Test {


        public  int total = 0;

        public  void add() {
            lock.lock();
            try {
                total++;
                System.out.println(total);
            } finally {
                lock.unlock();
            }
        }

        public void reduce() {
            lock.lock();
            try {
                total--;
            } finally {
                lock.unlock();
            }
        }
    }


    static class AddRunnable implements Runnable {

        private Test test;

        public AddRunnable(Test test) {
            this.test = test;
        }

        public void run() {
            test.add();
        }
    }
}

上面两种写法,第一种对应Synchronized锁类的情况,第二种对应Synchronized锁实例的情况。

总结

如果了解Lock、了解了AQS、了解各种锁的概念,发现再看实现类的时候,很多原来看不懂的内容都能看懂了,总而言之前面额学习还是有效果的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值