读写锁- ReentrantReadWriteLock

ReentrantLock 不管公平锁还是非公平锁都是独占锁,不管将要进行的业务是读操作还是写操作都必须获得锁

ReentrantReadWriteLock 就是读写分离的锁,多个线程如果都是读操作可以同时获取到锁

本章只分析 ReentrantReadWriteLock,AQS 从入门到精通

类结构

// ReadWriteLock 接口提供了两个方法 readLock() 和 writeLock()
public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
}

类成员

类属性

类成员都是内部类,没啥好说的,还有 Unsafe 类的引用,就不贴出来了

private final ReentrantReadWriteLock.ReadLock readerLock;

private final ReentrantReadWriteLock.WriteLock writerLock;

final Sync sync;

内部类

abstract static class Sync extends AbstractQueuedSynchronizer {}

// 非公平锁
static final class NonfairSync extends Sync {

    final boolean writerShouldBlock() { return false; }
  
    final boolean readerShouldBlock() { return apparentlyFirstQueuedIsExclusive(); }
}
    
// 公平锁
static final class FairSync extends Sync {

    final boolean writerShouldBlock() { return hasQueuedPredecessors(); }
  
    final boolean readerShouldBlock() { return hasQueuedPredecessors(); }
}

// 读锁
public static class ReadLock implements Lock, java.io.Serializable {

    private final Sync sync;

  	// 获取锁,获取不到就入队
    public void lock() { sync.acquireShared(1); }

  	// 获取读锁,获取到就算了,不会入队
    public boolean tryLock() { return sync.tryReadLock(); }

  	// 同上,带了个时间,超时也就算了
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

  	// 释放锁
    public void unlock() { sync.releaseShared(1); }

  	// 读锁不支持条件变量。条件变量的设计遵循 wait()、notify(),所以只能独占模式才能使用
    public Condition newCondition() { throw new UnsupportedOperationException(); }
}

// 写锁
public static class WriteLock implements Lock, java.io.Serializable {

    private final Sync sync;

  	// 获取锁,获取不到就入队
    public void lock() { sync.acquire(1); }

  	// 获取读锁,获取到就算了,不会入队
    public boolean tryLock( ) { return sync.tryWriteLock(); }

  	// 同上,带了个时间,超时也就算了
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

  	// 释放锁
    public void unlock() { sync.release(1); }

  	// 支持条件变量
    public Condition newCondition() { return sync.newCondition(); }

}

原理

加写锁

// java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock#lock
public void lock() {
    sync.acquire(1);
}
 
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
    if (!tryAcquire(arg) && // 获取锁
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 获取锁失败,线程入队并挂起,AQS 提供
        selfInterrupt();
}
 
// java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire
protected boolean tryAcquire(int arg) { // 模板方法
    throw new UnsupportedOperationException();
}
 
// java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryAcquire
protected final boolean tryAcquire(int acquires) { // 获取锁原理
  
    Thread current = Thread.currentThread();
    int c = getState(); // 锁状态(低16位表示写锁,高16位表示读锁)
    int w = exclusiveCount(c); // 写锁状态
  
    if (c != 0) { // state 不等于0(两种情况:1 可能写锁被别的线程持有;2 可能读锁被别的线程持有)
        // w==0表示写锁未被持有,说明读锁就已经被持有了,这里要获取写锁,因为读写互斥所以获取失败(除非当前线程获取的读锁)
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 走到这里说明当前线程就是持有读锁的线程,可以获取写锁,但不能超过最大值 65535(16位最大值)
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");

        setState(c + acquires); // 修改 state
        return true;
    }
    // 如果 c=0,说明读锁和写锁都还在,可以获取(虽然资源空闲但不一定就一定获取)
    if (writerShouldBlock() || // 是否需要阻塞(公平锁:如果队里有其他节点,当前线程入队;非公平:尝试插队)
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

加读锁

// java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock#lock
public void lock() {
    sync.acquireShared(1);
}
 
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireShared
public final void acquireShared(int arg) {
    // tryAcquireShared 方法根据返回值获取锁(可能返回 -1,0,1,对于读写锁只返回 1 和 -1)
    if (tryAcquireShared(arg) < 0) // -1 表示获取锁失败,1 表示获取锁成功
        doAcquireShared(arg); // 如果获取锁失败走这里,入队和阻塞线程,AQS 提供
}
 
// java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquireShared
protected int tryAcquireShared(int arg) { // 模板方法
    throw new UnsupportedOperationException();
}
 
// java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryAcquireShared
protected final int tryAcquireShared(int unused) {
    
    Thread current = Thread.currentThread();
    int c = getState(); // 资源状态
    if (exclusiveCount(c) != 0 && // 判断写锁是否被某个线程持有
        getExclusiveOwnerThread() != current) // 持有写锁的线程是不是当前线程
        return -1; // 写锁已被别的线程持有,不能获取读锁,返回 -1,获取锁失败(读写互斥)
  
    // 读锁获取成功后的逻辑(走到这里说明写锁未被获取或获取写锁的线程就是自己,这时能获取读锁)
    int r = sharedCount(c); // 读锁状态(多个线程可以获取,也能单个线程重入)
    if (!readerShouldBlock() && // 是否需要阻塞(队列中第二个节点是否等待写锁,如果是就要阻塞,不然就不阻塞)
        r < MAX_COUNT && // 不阻塞,也要验证读锁状态不能大于 65535
        compareAndSetState(c, c + SHARED_UNIT)) { // 修改 state
        if (r == 0) { // 读锁是0
            firstReader = current;
            firstReaderHoldCount = 1; // 重入次数为1
        } else if (firstReader == current) { // 读锁不是0,但是持有读锁的线程就是当前线程
            firstReaderHoldCount++; // 重入次数++
        } 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);
}

使用示例

读读共享

public class Test {
 
    public static void main(String[] args) {
        Resource resource = new Resource();
        
        new Thread(() -> {
            resource.read();
        }, "线程A").start();
 
        new Thread(() -> {
            resource.read();
        }, "线程B").start();
    }
 
}
 
class Resource{
    private final ReentrantReadWriteLock rrw =  new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rrw.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = rrw.writeLock();
 
    public void read(){
        readLock.lock();
        System.out.println("获取读锁");
        try {
            System.out.println("读取数据....");
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            System.out.println("读取完成,释放读锁");
            readLock.unlock();
        }
    }
}

读写分离

public class Test {
 
    public static void main(String[] args) {
        Resource resource = new Resource();
        
        new Thread(() -> {
            resource.read();
        }, "线程A").start();
        
        // 休眠 100 毫秒
        try {
            TimeUnit.MILLISECONDS.sleep(100L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
 
		// 获取写锁,要等读锁释放才能获取
        new Thread(() -> {
            resource.write();
        }, "线程B").start();
    }
 
}
 
class Resource{
    private final ReentrantReadWriteLock rrw =  new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rrw.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = rrw.writeLock();
 
    public void read(){
        readLock.lock();
        System.out.println("获取读锁");
        try {
            System.out.println("读取数据....");
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            System.out.println("读取完成,释放读锁");
            readLock.unlock();
        }
    }
 
    public void write(){
        writeLock.lock();
        System.out.println("获取写锁");
        try {
            System.out.println("写入数据....");
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            System.out.println("写入完成,释放写锁");
            writeLock.unlock();
        }
    }
}
posted @ 2023-05-24 16:02  CyrusHuang  阅读(33)  评论(0)    收藏  举报