详解java并发之重入锁-ReentrantLock
前言
目前主流的锁有两种,一种是synchronized,另一种就是reentrantlock,jdk优化到现在目前为止synchronized的性能已经和重入锁不分伯仲了,但是重入锁的功能和灵活性要比这个关键字多的多,所以重入锁是可以完全替代synchronized关键字的。下面就来介绍这个重入锁。
正文
reentrantlock重入锁是lock接口里最重要的实现,也是在实际开发中应用最多的一个,我这篇文章更接近实际开发的应用场景,为开发者提供直接上手应用。所以不是所有方法我都讲解,有些冷门的方法我不会介绍或一句带过。
一、首先先看声明一个重入锁需要使用到那几个构造方法
public reentrantlock() { sync = new nonfairsync(); } public reentrantlock(boolean fair) { sync = fair ? new fairsync() : new nonfairsync(); }
推荐声明方式
private static reentrantlock lock = new reentrantlock(true); private static reentrantlock locka = new reentrantlock();
重点说明:
reentrantlock提供了两个构造方法,对应两种声明方式。
第一种声明的是公平锁,所谓公平锁,就是按照时间先后顺序,使先等待的线程先得到锁,而且,公平锁不会产生饥饿锁,也就是只要排队等待,最终能等待到获取锁的机会。
第二种声明的是非公平锁,所谓非公平锁就和公平锁概念相反,线程等待的顺序并不一定是执行的顺序,也就是后来进来的线程可能先被执行。
reentrantlock默认是非公平锁,因为:公平锁实现了先进先出的公平性,但是由于来一个线程就加入队列中,往往都需要阻塞,再由阻塞变为运行,这种上下文切换是非常好性能的。非公平锁由于允许插队所以,上下文切换少的多,性能比较好,保证的大的吞吐量,但是容易出现饥饿问题。所以实际生产也是较多的使用非公平锁。
非公平锁调用的是nonfairsync方法。
二、加入锁之后lock方法到底是怎么处理的(只讲非公平锁)
刚才我们说如果是非公平锁就调用nonfairsync方法,那我们就来看看这个方法都做来什么。
static final class nonfairsync extends sync { private static final long serialversionuid = 7316153563782823691l; /** * performs lock. try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareandsetstate(0, 1)) setexclusiveownerthread(thread.currentthread()); else acquire(1); } protected final boolean tryacquire(int acquires) { return nonfairtryacquire(acquires); } }
重点说明:
读前先知:reentrantlock用state表示“持有锁的线程已经重复获取该锁的次数”。当state(下文用状态二子代替)等于0时,表示当前没有线程持有锁)。
第一步调用compareandsetstate方法,传了第一参数是期望值0,第二个参数是实际值1,当前这个方法实际是调用了unsafe.compareandswapint实现cas操作的,也就是上锁之前状态必须是0,如果是0调用setexclusiveownerthread方法
private transient thread exclusiveownerthread; protected final void setexclusiveownerthread(thread thread) { exclusiveownerthread = thread; }
可以看出setexclusiveownerthread就是线程设置为当前线程,此时说明有一名线程已经拿到了锁。大家都是cas有三个值,如果旧值等于预期值,就把新值赋予上,所以当前线程得到了锁就会把状态置为1。
第二步是compareandsetstate方法返回false时,此时调用的是acquire方法,参数传1
tryacquire()方法实际是调用了nonfairtryacquire()方法。
public final void acquire(int arg) { if (!tryacquire(arg) && acquirequeued(addwaiter(node.exclusive), arg)) selfinterrupt(); } 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; }
注释上说的很明白,请求独占锁,忽略所有中断,至少执行一次tryacquire,如果成功就返回,否则线程进入阻塞--唤醒两种状态切换中,直到tryacquire成功。详情见链接tryacquire()、addwaiter()、acquirequeued()挨个分析。
好,到日前为止大家清楚了lock()方法到调用过程,清楚了,为什么只有得到锁的当前线程才可以执行,没有得到的会在队列里不停的利用cas原理试图得到锁,cas很高效,也就是,为什么reentrantlock比synchronized高效的原因,缺点是很浪费cpu资源。
三、所有线程都执行完毕后调用unlock()方法
unlock()方法是通过aqs的release(int)方法实现的,我们可以看一下:
public void unlock() { sync.release(1); } public final boolean release(int arg) { if (tryrelease(arg)) { node h = head; if (h != null && h.waitstatus != 0) unparksuccessor(h); return true; } return false; }
tryrelease()是由子类实现的,我们来看一下reentrantlock中的sync对它的实现:
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; }
先通过getstate获得状态标识,如果这个标识和要释放的数量相等,就会把当前占有锁的线程设置为null,实现锁的释放,然后返回true,否则把状态标识减去releases再返回false。
以上所述是小编给大家介绍的java并发之重入锁-reentrantlock详解整合,希望对大家有所帮助