游戏开发者联盟

java可重入锁中的owner为什么不是volatile的?

问题描述

ReentrantLock中使用了AbstractQueuedSynchronizer (简称AQS吧),AQS中有个exclusiveOwnerThread,很多人都看过这段代码,不知道有没有注意到exclusiveOwnerThread既不是volatile的,也不是atomic的,那这个owner的赋值和读写还线程安全么?

由来

我为什么会想到这个问题呢?
今天我用类似的机制,在c++里写了一个类似的重入锁,在windows上和std::recursive_lock对比,切换效率基本一致。
我就用java ReentrantLock写了相同的测试用例,发现ReentrantLock切换速度是recursive_lock的6倍左右(320个线程)。线程数越多,差距越明显。
我就对比了一下自己写的锁和ReentrantLock的区别,发现最大差别就在owner的定义上,java中ownder是个普通变量,自己定义了一个atomic变量。

于是我就先把owner也改成了普通变量,先测试了一下,运行结果也正确,切换速度基本也和ReentrantLock一致。

于是,我就想,这个owner这样写安全么?

分析

自己分析了一会代码,也搜了一下别人的解释,基本可以确定,这样是安全的(因为Doug Lea就是这么写的,肯定安全)。先贴出java代码:

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

int c = getState(); 肯定是安全的,它的修改受到锁的保护,只有锁持有者才有权限修改;本身又是volative变量,其它线程一定会拿到最新值。

owner的修改类似state , 受锁保护,是安全的;但是owner的读取,则是普通变量的读取(这里很不同)。

如果 if (c == 0),是需要抢锁的,这里我们不分析。
如果c!=0,这里要读取owner,我着重分析这里。c!=0,分两种情况:

  1. 当前线程持有锁:那么自己写owner,自己读,毫无疑问,可见性没问题,肯定是得到本线程的引用,比较后,可以进入锁。
  2. 当前线程没有持有锁:那么它读到的是一个无意义的值,而且这个值肯定不是当前线程引用。因为只有自己写入owner,owner的值才会是当前线程,一旦自己退出临界区,owner就会被自己写成null。所以此时无论它读到的是哪个引用,都不可能是自己。而且加载进来的也不太可能是null和一个引用高低4字节的混合值,这是由对齐和缓存行机制保证的。

结尾

花了不少时间,理解这种代码,是个细致活。同时不得不感叹juc包的强大,细节上做的如此到位,又如此易用。