浅析ReentrantLock可重入锁

Lock锁接口在JAVA SE5之后,出现在并发包中.它提供了与synchronized关键字一样的同步功能.只是在使用时需要显式地获取和释放锁,缺点就是缺少像synchronized那样隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。

可重入锁和不可重入锁

  • 可重入锁:一个线程调用一个加锁的方法后,还可以调用其他加同一把锁的方法.
  • 不可重入锁:一个线程获取到一把锁之后,该线程是无法调用其他加了该锁的方法.这种情况,容易造成死锁.比如:方法一添加了不可重入锁,方法二添加了不可重入锁,并且调用了方法一.这种情况下,调用方法二就会出现死锁的情况.那么.重入锁就可以避免这种情况.
  • 常用的可重入锁:
    Sychronized,java.util.concurrent.locks.ReentrantLock

ReentrantLock基本使用

ReentrantLock对资源进行加锁,同一时刻只会有一个线程能够占有锁.当前锁被线程占有时,其他线程会进入挂起状态,直到该锁被释放,其他挂起的线程会被唤醒并开始新的竞争.

public class ReentrantLockExample {
    private ReentrantLock reentrantLock = new ReentrantLock();

    private void test(){
        reentrantLock.lock();
        System.out.println("进行原子操作");
        try {
            Thread.sleep(3000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        ReentrantLockExample reentrantLockExample = new ReentrantLockExample();
        for (int i = 0; i < 5; i++) {
            executorService.submit(new Thread(reentrantLockExample::test));
        }
    }
}

通过控制台的输出信息可知:每隔三秒输出一次信息.

ReentrantLock很大程度的依赖了抽象类AbstractQueuedSynchronizer,这里需要先对AbstractQueuedSynchronizer进行了解.看我下面的这篇文章

ReentrantLock又有公平锁和非公平锁之分,所以可以看到在源码中有两个锁的实现


公平锁:每个线程的资源竞争是公平的.按照自身线程调用lock方法的顺序来获取锁,即先到先得.
非公平锁:每个线程的资源竞争是顺序不定,谁的优先级高,那么谁就会先获得锁.

ReentrantLock几个重要方法的剖析

  • 构造方法:ReentrantLock的无参构造器,默认将局部变量sync赋值为NonfairSycn.
/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }
  • lock():调用sycn内部类的lock方法.
1.获取一把锁,如果该锁没有被其他线程持有,则立即返回,并将锁计数器加一;
2.如果当前线程已经持有该锁,则将锁计数器加一,并立即返回.
3.如果该锁被其他线程持有,那么当前线程的目的将会无效并进入休眠状态.直到锁计数器的值为1.
public void lock() {
        sync.lock();
    }

这里先看一下,公平锁的lock方法的实现.

FairSync:默认调用AbstractQueuedSynchronizer的acquire()方法.
final void lock() {
            acquire(1);
        }
AbstractQueuedSynchronizer:
public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

非公平锁的lock方法实现:

NonfairSync:
final void lock() {
        //通过CAS操作,将AQS中的stateOffset从0改为1.
        if (compareAndSetState(0, 1))
             //将当前线程设为独享线程
              setExclusiveOwnerThread(Thread.currentThread());
        else
             //否则,再次请求同步状态.一般参数为0是释放锁,参数为1是获取锁
              acquire(1);
}
AbstractQueuedSynchronizer:其中tryAcquire()是由具体的实现类实现的.
public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}
这里分为两步进行分析:
1.NonfairSync:执行tryAcquire方法
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        //获取当前AQS的状态
        int c = getState();
        if (c == 0) {//同步状态为0时,执行CAS操作
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }//当前线程已经获取到锁,因为当前是重入锁,则state+1,并返回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.NonfairSync:tryAcquire方法返回false之后,会执行acquireQueued方法.
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //尾节点不为空
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

因为NonfairSync是非公平锁,所以对于新来的线程和同步队列的线程,都能调用这个方法来获取到锁.
由于实现过于复杂,这里总结来说:当多个线程赖竞争锁时,只会有一个线程能够获取到锁,那么其他线程将会通过CAS操作(保证数据的一致性)来将当前线程添加到同步队列中,当所有线程进入到同步队列之后,就会进入自旋状态(死循环判断当前线程所在节点的前驱节点是否为head节点)去尝试获取同步状态.

  • lockInterruptibly()或者tryLock()
    可中断的获取方式.两者最终都会调用方法:
private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        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;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

如果检测到线程的中断,将会直接抛出异常.

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容