java并发包之ReentrantLock

一、前言

在上文中谈到了AQS是Lock实现的前提,而本文说到的ReetrantLock就是在此基础上处理的。而本文中会对ReetrantLock公平性,可重入性等进行介绍。

二、特性

2.1 可重入

可重入性也就是能够让线程多次进行锁的获取操作,首先举个例子:

public class ReentranDemo {

    private ReentrantLock lock = new ReentrantLock();

    public void putDemo(){
        try {
            lock.lock();
            System.out.println("putDemo"+Thread.currentThread().getName());
            reInDemo();
        }finally {
            lock.unlock();
        }
    }

    public void reInDemo(){
        lock.lock();
        try {
            System.out.println("reInDemo"+Thread.currentThread().getName());
        }finally {
            lock.unlock();
        }
    }
     public static void main(String[] args) {
            new Thread(){
                public void run(){
                    new ReentranDemo().putDemo();
                }
            }.start();
    }
}

运行结果

putDemoThread-0
reInDemoThread-0

如果ReentrantLock不是可重入锁的话,那么该线程会进入死锁。

2.2 公平性

公平锁:线程获取锁资源的顺序为先后调用lock方法的顺序依次获取锁
非公平锁:加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待

看个例子:

public class FairLockDemo {

    private static Lock fairLock = new ReentrantLock(true);
    public void fair() {
        for (int i = 0; i < 5; i++) {
            new Thread(){
                public void run(){
                    for (int i = 0; i < 5; i++) {
                        fairLock.lock();
                        try {
                            System.out.println(Thread.currentThread().getName() + "is locking " );
                        } finally {
                            fairLock.unlock();
                        }
                    }
                }
            }.start();

        }

    }
    public static void main(String[] args) {
        FairLockDemo fairLockDemo = new FairLockDemo();
        fairLockDemo.fair();
    }  
}

返回结果如下:

Thread-0is locking 
Thread-1is locking 
Thread-2is locking 
Thread-3is locking 
Thread-0is locking 
Thread-4is locking 
Thread-1is locking 
Thread-2is locking 
Thread-3is locking 
Thread-0is locking 
Thread-4is locking 
Thread-1is locking 
Thread-2is locking 
Thread-3is locking 
Thread-0is locking 
Thread-4is locking 
Thread-1is locking 
Thread-2is locking 
Thread-3is locking 
Thread-0is locking 
Thread-4is locking 
Thread-1is locking 
Thread-2is locking 
Thread-3is locking 
Thread-4is locking 

如例子所示,连续获取的情况基本没有

2.3 非公平锁

加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待

看个例子:

public class UnfairLockDemo {

    private static Lock unfairLock = new ReentrantLock();

    public void unfair() {
        for (int i = 0; i < 5; i++) {
            new Thread(){
                public void run(){
                    for (int i = 0; i < 5; i++) {
                        unfairLock.lock();
                        try {
                            System.out.println(Thread.currentThread().getName() + "is locking " );
                        } finally {
                            unfairLock.unlock();
                        }
                    }
                }
            }.start();

        }

    }

    public static void main(String[] args) {
        UnfairLockDemo unfairLockDemo = new UnfairLockDemo();
        unfairLockDemo.unfair();
    }

}

返回结果如下:

Thread-0is locking 
Thread-3is locking 
Thread-3is locking 
Thread-3is locking 
Thread-3is locking 
Thread-4is locking 
Thread-4is locking 
Thread-4is locking 
Thread-4is locking 
Thread-4is locking 
Thread-1is locking 
Thread-1is locking 
Thread-1is locking 
Thread-1is locking 
Thread-1is locking 
Thread-2is locking 
Thread-2is locking 
Thread-2is locking 
Thread-2is locking 
Thread-2is locking 
Thread-0is locking 
Thread-0is locking 
Thread-0is locking 
Thread-0is locking 
Thread-3is locking 

而我们的非公平锁出现连续获取锁的情况却非常多。而出现这种情况的原因会在后文源码分析中介绍到。

三、主要类与方法分析

3.1 Sync

在看源码之前我们先略微介绍下该类,这个类是继承于AQS,而Sync还有两个子类,分别是公平锁和非公平锁的两种不同的实现。

 abstract static class Sync extends AbstractQueuedSynchronizer {

        //具体实现由非公平和公平进行实现
        abstract void lock();
        //非公平尝试获取
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //获取AQS锁资源的数量
            int c = getState();
            //如果为0表示没有线程获取资源
            if (c == 0) {
                //利用CAS将state为0修改为当前资源数,并将当前的线程记录下来
                if (compareAndSetState(0, acquires)) {       
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果state>0且当前线程和记录的线程是同一个,则重入处理(这里就是重入锁的相应实现)
            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 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;
        }
        //是否独占
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
        //获取持有该锁的线程
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
        //获取重入的线程数
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {
            return getState() != 0;
        }
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); 
        }
    }

nonfairTryAcquire在后文会看到非公平锁对其的调用,而内部实现的处理正是我们可重入锁的具体处理方案。

3.2 FairSync

接下来是我们的公平锁的类,他实现了其父类Sync的lock方法。也同时对上一篇文章AQS的tryAcquire进行了重写

static final class FairSync extends Sync {
        //获取锁,内部主要的实现由AQS的acquire实现
        final void lock() {
            //表示会获取一个锁资源,其中一部门就是由FairSync类下的tryAcquire实现具体实现可以看下上一篇文章AQS的acquire方法
            acquire(1);
        }
        //对基类AQS的重写,表示获取资源
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //状态为0表示没有线程获取锁
            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;
        }
    }

    public final boolean hasQueuedPredecessors() {
        Node t = tail;
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

由于lock的主要逻辑在AQS的acquire中,而tryAcquire方法的重写,展示了Reetrantlock公平锁在获取资源与其它同步锁不同的特性。通过调用的acquire表示其是一个独占锁,而通过判断当前线程与记录线程的比较实现的是Reetrantlock可重入性。而最后判断当前节点是否有前置节点来实现了我们的公平性。

3.3 NonfairSync

static final class NonfairSync extends Sync {
       
        final void lock() {
            //如果能获取锁直接获取。
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //获取不了通过nonfairTryAcquire进行锁资源获取
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            //sync类中有实现
            return nonfairTryAcquire(acquires);
        }
    }

公平锁和非公平锁的区别:
首先公平锁获取直接调用acquire方法,而该方法不是直接获取锁,而是调用的tryAcquire方法,而公平锁的tryAcquire首先获取state状态吐过没有线程占用(state=0),会判断在AQS的等待队列中的该节点的前置节点是否为其他同样等待的节点,如果有就说明在公平性上其他节点已经排好队了,那么会让出获取资源的权利。
而非公平锁,一上来就直接判断state状态,完全是一种抢占的机制。如果抢占不了在调用acquire方法,而非公平的acquire中的tryAcquire同样不会像公平锁的tryAcquire方法判断节点的前置节点。

3.4构造函数

    public ReentrantLock() {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

可以指定ReentrantLock的公平性

3.5 lock方法

 public void lock() {
        sync.lock();
    }

直接调用公平锁或者非公平锁的lock

3.6 tryLock

public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

尝试获取锁,不会发生阻塞,成功或失败直接返回。

3.7 unlock

public void unlock() {
    sync.release(1);
}

释放锁直接调用AQS的release释放。

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

推荐阅读更多精彩内容