Java中的显式锁

Lock接口

在java5之前,要实现同步只能用synchronize,在java5后,随着并发工具包的出现,出现了另一种同步方式--显式锁,显式锁提供了更丰富,粒度更细的加锁方式,其中的"锁王"就是--Lock接口.

先看看它的方法列表:

方法 介绍
lock() 获取锁,如果没有锁可用,就一直阻塞
lockInterruptibly() 获取锁,直到当前线程被中断
newCondition() 返回绑定到此锁的Condition实例(用于线程通信,使用方法详见线程通信及其工具类)
tryLock() 获取锁,若获取到立刻返回true,否则立刻返回false
tryLock(long time, TimeUnit unit) 在指定时间内获取锁,超时返回false
unlock() 释放锁

Lock使用方式一般如下:

 Lock l = ...;
 l.lock();
 try {
   // access the resource protected by this lock
 } finally {
   l.unlock();//注意unlock要放在finally块内,否则出现异常锁得不到释放
 }
Lock与synchronize的区别

既然有了synchronize,为什么还要Lock?我们来看看Lock与synchronize的区别:

  1. 底层实现不同:
  • synchronize是jvm支持的关键字,实现原理是同步监视器(详见...)
  • Lock的底层实现依赖AQS(详见浅析AQS)
  1. 使用方法不同
  • synchronize不用手动释放锁
  • Lock需要手动释放锁
  1. Lock比synchronize提供更丰富的加锁方法以及更细的粒度控制
  • Lock可调用lockInterruptibly实现中断等待获取锁
  • Lock可调用tryLocktryLock(long time, TimeUnit unit)实现获取锁等待时间的控制
  • Lock的实现类大部分提供了公平锁和非公平锁的实现,而synchronize是非公平的
  1. Lock可实现多个条件绑定,实现精确唤醒,而synchronize只能随机唤醒

线程八锁--显式锁的实现

先看看Lock的继承体系


Lock继承体系

表面上看,Lock只有可重入锁,读写锁的实现类,实际上可重入锁和读写锁都有其内部类实现公平锁和非公平锁

可重入锁与非重入锁

可重入锁又被称为递归锁.在jdk中,可重入锁显式锁的实现类是ReentrantLock

可重入锁类图.png

ReentrantLock实现了Lock接口,且有三个内部类分别是ReentrantLock.FairSync,ReentrantLock.NonfairSync和ReentrantLock.Sync

ReentrantLock.FairSync,ReentrantLock.NonfairSync分别是ReentrantLock的公平锁实现和非公平锁实现

可重入的意思是,同一个线程可以多次获取同一个锁,举个例子

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    System.out.println("第一次获取锁");
    //这里重复获取了同一把锁
    lock.lock();
    try {
        System.out.println("第二次获取锁");
    } finally {
        lock.unlock();
    }
} finally {
    lock.unlock();
}

若上述代码不是可重入锁,则会在第二次获取锁时,发生死锁,因为在等待第一次获取的锁释放,而第一次获取的锁要在第二次获取锁后才释放

jdk中所有的锁都是可重入锁,包括synchronized

那么我们再来看看,可重入锁是怎么实现的

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

可以看到是调用了内部类ReentrantLock.Sync的lock(),而ReentrantLock.Sync又有公平锁和非公平锁实现,我们看看公平锁实现

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

这个acquire()正是AQS的方法,不了解AQS的同学可以看看浅析AQS,继续往里深挖

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

可以看到在acquire方法里首先执行tryAcquire方法,这个方法需要子类覆盖否则直接抛异常,所以我们要看的是ReentrantLock.FairSync里的tryAcquire方法

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    //调用AQS的getState方法获取线程状态
    int c = getState();
    //若线程状态为0,表示当前线程还没有获取锁
    if (c == 0) {
        if (/** 此方法是查看有没有其他线程排在自己签名 **/
                !hasQueuedPredecessors()
                        &&
                        /**CAS设置state为1,回溯前面代码,传的值是1**/
                        compareAndSetState(0, acquires)) {
            //设置当前线程拥有独占访问权
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //若当前线程已拥有独占访问权
    else if (current == getExclusiveOwnerThread()) {
        //使state++,这里就是重入锁的关键
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

上面这段代码大概意思就是:当前线程如果还未获取锁,那就尝试获取(查看有无线程排在自己前面),若果已获取了锁,就让state++.所以state=0时,线程无锁,state>0时,state的值就表示该线程获取重入锁的次数.同理,若释放重入锁,state--

//ReentrantLock类
public void unlock() {
    sync.release(1);
}
//AQS类
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//唤醒后继线程
        return true;
    }
    return false;
}
//ReentrantLock.Sync类
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;//重入计数减1
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {//若state为0,则释放锁
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
公平锁与非公平锁

jdk里的公平锁与非公平锁不是一个具体类,而是一种锁的公平实现与非公平实现,回顾一下可重入锁的类图

可重入锁类图.png

ReentrantLock.FairSync,ReentrantLock.NonfairSync就是ReentrantLock的公平实现和非公平实现

公平锁与非公平锁之间的区别就是在获取锁的时候,非公平锁会先尝试"插队","插队"失败就和公平锁一样排队等待,但在每次有机会获取锁时,非公平锁都会尝试"插队"

//公平锁实现
final void lock() {
    acquire(1);
}
//非公平锁实现
final void lock() {
    if (compareAndSetState(0, 1)) //插队
        setExclusiveOwnerThread(Thread.currentThread());//若插队成功,则赋予当前线程访问权
    else
        acquire(1);
}

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

//公平版tryAcquire
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;
}

//非公平版tryAcquire
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

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

参考文献

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

推荐阅读更多精彩内容