并发编程 — 6.4 JUC — 可重入锁

ReentrantLock

ReentrantLock实现了Lock接口,而ReentrantLock其实相当于一副空壳,它的主要功能就是控制构造出公平锁还是非公平锁,对锁的相关操作细节都是由内部类Sync来完成。Sync继承自AQS(AbstractQueuedSynchronizer),并再衍生出两个内部类非公平锁NofairSync和公平锁FairSync。
通过ReentrantLock的的默认构造函数为非公平锁,也可通过含参构造函数来控制其构造公平锁。

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

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

两种锁的主要区别就在于lock操作上,FairSync的lock就是直接通过AQS的acquire函数来尝试获取同步状态,但是NofairSync的lock会先尝试性的直接去获取同步状态,如果失败再通过acquire函数进行获取。

//公平锁FairSync的锁操作
final void lock() {
    acquire(1);
}


//非公平锁NonfairSync的锁操作
final void lock() {
    //直接尝试获取同步状态
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

AQS的acquire函数中又会调用子类的tryAcquire函数来尝试获取同步状态,而两种锁在tryAcquire函数上又有不同。

//公平锁FairSync的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;
                }
            }
			//在这里可以看出可重入的思想,如果当前线程就是持有锁的线程,则直接将状态state + 1
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }


//非公平锁nonfairTryAcquire的锁操作
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
              //非公平锁不管同步队列有没有节点,而是直接尝试获取同步状态
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //在这里可以看出可重入的思想,如果当前线程就是持有锁的线程,则直接将状态state + 1
            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;
        }

ReentrantReadWriteLock

ReentrantReadWriteLock实现自ReadWriteLock(读写锁)的接口。ReentrantReadWriteLock主要使用在数据的读取操作远多于写操作的情况下,可以提高并发性。它具有如下特征:

a. 维护了一对锁ReadLock(读锁)和WriteLock(写锁)读锁在同一时刻允许多个线程持有,而写锁在被持有时,其他尝试获取读锁或者写锁的线程都会被阻塞。读锁的lock方法会直接调用sync的acquireShared方法,从而调用tryAcquireShared方法:

//读锁获取同步状态
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    //如果当前写锁数量不为0且获取到同步状态的线程不是当前线程,则直接返回-1。根据AQS的流程,此线程会被加入到同步队列中。
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    //获取读锁数量
    int r = sharedCount(c);
    //按照当前锁的模式(公平锁/非公平锁)判断线程是否需要阻塞。
    //如果不需要阻塞,且读数量小于最大数量,尝试CAS获取同步状态
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        //如果读锁数量为0,则当前线程为第一个读线程
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } 
        //如果数量不为0,但是当前线程就是第一个读线程(说明是重入了),设置第一个读线程的重入次数。
        else if (firstReader == current) {
            firstReaderHoldCount++;
        } 
        //其他读线程(非第一个)的重入次数设置。通过在每个线程中的内存空间保存HodlerCount类(用于记录当前线程获取锁的次数),来获取相应的次数
        else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        //获取同步状态成功
        return 1;
    }
    //获取读锁失败后,进入自旋继续尝试
    return fullTryAcquireShared(current);
}

写锁的lock方法则会调用sync的tryAcquire方法:

//写锁获取同步状态
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    //获取状态:读 + 写
    int c = getState();
    //通过状态获取写数量
    int w = exclusiveCount(c);
    //同步状态不为0,说明有线程持有同步状态
    if (c != 0) {
        //如果写数量为0(说明有线程在读)或者 写数量不为0(说明没有读线程,只有写线程)但是写线程不是当前线程,则返回失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        //来到这里说明是写线程重入,则判断写数量是否超出最大值,超出抛异常
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        //当前线程成功获取写锁,返回成功
        setState(c + acquires);
        return true;
    }
    /*
	*同步状态为0,说明没有线程在读写。则按照当前锁的模式(公平锁/非公平锁)判断线程是否需要阻塞。
    *如果需要阻塞,或者不需要阻塞但是没有成功获取同步状态,返回失败
    */
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    //说明成功获取同步状态,设置当前线程为持有锁线程,并返回成功
    setExclusiveOwnerThread(current);
    return true;
}

b. 还维护了一个Sync,所有的锁操作都是由Sync完成的,也就是说读锁和写锁共同使用一个同步队列和一个同步状态state(在Sync里面)。在ReentrantReadWriteLock的同步队列中,将同步状态state分成了高16位(读状态)和低16位(写状态),通过位运算来获取值。

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

//获取读数量,通过将state右移1位得到
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
//获取写数量,通过将state和0xFFFF进行与操作来得到
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

写操作虽然会阻塞其他的写操作,但由于ReentrantReadWriteLock是可重入的锁,所以写数量会在该锁被重入的时候用于记录重入次数。

c. Sync内部含有子类NofairSync和FairSync,也就是说ReentrantReadWriteLock也是支持公平锁和非公平锁的。

public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
}

ThreadLocal

在ReentrantReadWriteLock的readLock中,通过一个HoldCounter类来记录当前线程的重入次数,然后将其存储到readHolds中。而readHolds是在ReentrantReadWriteLock定义的一个内部静态类ThreadLocalHoldCounter,继承自ThreadLocal<HoldCounter>。
ThreadLocal是一种线程的本地存储,它为变量在每个线程中都创建了一个实例,存储在共用的ThreadLocalMap中(以线程为键,实例为值)。ThreadLocal是线程共享的,但里面存储的变量却不是共享的,因此ThreadLocal并不是用来解决多线程变量共享的问题,它只是用来方便用户存储和读取的一种存储结构。

锁降级

锁降级是指把持住当前拥有的写锁(进行数据写入)的同时,再获取到读锁进行数据读取使用,然后再释放写锁,最后释放读锁。这样做的目的是保证数据的可见性,防止本线程在数据完成写操作后,再进行读取前有其他线程修改了数据,从而让本线程先获取到读锁以保证其他线程无法加写锁,然后本线程再释放写锁,读取数据。

//这是Oracle官方的示例,体会一下。
class CachedData {
   Object data;
   volatile boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        try {
          // Recheck state because another thread might have
          // acquired write lock and changed state before we did.
          if (!cacheValid) {
            data = ...
            cacheValid = true;
          }
          // Downgrade by acquiring read lock before releasing write lock
          rwl.readLock().lock();
        } finally {
          rwl.writeLock().unlock(); // Unlock write, still hold read
        }
     }

     try {
       use(data);
     } finally {
       rwl.readLock().unlock();
     }
   }
 }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值