ReentrantReadWriteLock(1)

当读操作远远高于写操作时,这时候使用读写锁让读-读可以并发,提高性能。

这是什么意思呢?我们知道synchronized的作用标记一个方法的时候,在并发情况下,只允许一个线程去访问,其他线程在外面等待。可这很影响性能。即使这个方法是提供读取,但也只能一个线程去访问。

@Slf4j
public class TestReadWriteLock {
    public static void main(String[] args) throws InterruptedException {
        DataContainer dataContainer = new DataContainer();
        new Thread(() -> {
            dataContainer.read();
        }, "t1").start();

        new Thread(() -> {
            dataContainer.write();
        }, "t2").start();

        new Thread(() -> {
            dataContainer.write();
        }, "t3").start();

        new Thread(() -> {
            dataContainer.read();
        }, "t4").start();
    }
}

@Slf4j
class DataContainer {
    private Object data;
    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock r = rw.readLock();
    private ReentrantReadWriteLock.WriteLock w = rw.writeLock();

    public Object read() {
        log.debug("准备获取读锁...");
        r.lock();
        try {
            log.debug("读取中");
            sleep(1);
            return data;
        } finally {
            log.debug("释放读锁...");
            r.unlock();
        }
    }


    public void write() {
        log.debug("准备获取写锁...");
        w.lock();
        log.debug("w入了");
        try {
            sleep(1);
        } finally {
            log.debug("释放w锁...");
            w.unlock();
        }
    }
}

结果如下图=

17:00:11.867 c.DataContainer [t2] - 准备获取写锁...
17:00:11.867 c.DataContainer [t1] - 准备获取读锁...
17:00:11.867 c.DataContainer [t3] - 准备获取写锁...
17:00:11.867 c.DataContainer [t4] - 准备获取读锁...
17:00:11.870 c.DataContainer [t3] - w入了
17:00:12.871 c.DataContainer [t3] - 释放w锁...
17:00:12.871 c.DataContainer [t2] - w入了
17:00:13.871 c.DataContainer [t2] - 释放w锁...
17:00:13.871 c.DataContainer [t4] - 读取中
17:00:13.871 c.DataContainer [t1] - 读取中
17:00:14.872 c.DataContainer [t4] - 释放读锁...
17:00:14.872 c.DataContainer [t1] - 释放读锁...

会发现:

读操作的线程,打印信息都是同一时刻,不需要等待。
写操作的线程,有先后顺序,也就是说,需要等待,锁在着效果。

先看读操作源码:获取锁和释放锁

  //第一步:
  public void lock() {
            this.sync.acquireShared(1);
        }
  //没锁前默认锁状态为0,且读锁和写锁标记不一样。读锁是共享,写锁是独占。
  
  //第二步:tryAcquireShared方法判断有没有给其他线程占据锁
  public final void acquireShared(int var1) {
        if (this.tryAcquireShared(var1) < 0) {
            this.doAcquireShared(var1);
        }

    }
 //第三步
 protected final int tryAcquireShared(int var1) {
            //获取当前线程信息
            Thread var2 = Thread.currentThread();
            //获取当前锁状态
            int var3 = this.getState();
            //如果当前状态不等于0,给写锁占据,且占据锁的不是当前线程,返回-1表示失败。
            //
            if (exclusiveCount(var3) != 0 && this.getExclusiveOwnerThread() != var2) {
                return -1;
            } else {
            //如果是占据锁的是当前线程或者锁的状态为零,且锁重入的次数小于65535,则cas改变锁重入的次数。返回1,表示成功。后面的线程由于读锁发生共享,共享次数发生改变,每次自增1.
                int var4 = sharedCount(var3);
                if (!this.readerShouldBlock() && var4 < 65535 && this.compareAndSetState(var3, var3 + 65536)) {
                    if (var4 == 0) {
                        this.firstReader = var2;
                        this.firstReaderHoldCount = 1;
                    } else if (this.firstReader == var2) {
                        ++this.firstReaderHoldCount;
                    } else {
                        ReentrantReadWriteLock.Sync.HoldCounter var5 = this.cachedHoldCounter;
                        if (var5 != null && var5.tid == ReentrantReadWriteLock.getThreadId(var2)) {
                            if (var5.count == 0) {
                                this.readHolds.set(var5);
                            }
                        } else {
                            this.cachedHoldCounter = var5 = (ReentrantReadWriteLock.Sync.HoldCounter)this.readHolds.get();
                        }

                        ++var5.count;
                    }

                    return 1;
                } else {
                    return this.fullTryAcquireShared(var2);
                }
            }
        }

读锁释放锁:

 //第一步:因为锁成功会加1,锁重入次数也会加1.所以释放的时候,要每次减1.
 public final boolean releaseShared(int var1) {
        if (this.tryReleaseShared(var1)) {
            this.doReleaseShared();
            return true;
        } else {
            return false;
        }
    }
 //对当前线程锁状态进行减操作,判断锁的重入次数是否全部释放完成。
 protected final boolean tryReleaseShared(int var1) {
            Thread var2 = Thread.currentThread();
            int var4;
            if (this.firstReader == var2) {
                if (this.firstReaderHoldCount == 1) {
                    this.firstReader = null;
                } else {
                    --this.firstReaderHoldCount;
                }
            } else {
                ReentrantReadWriteLock.Sync.HoldCounter var3 = this.cachedHoldCounter;
                if (var3 == null || var3.tid != ReentrantReadWriteLock.getThreadId(var2)) {
                    var3 = (ReentrantReadWriteLock.Sync.HoldCounter)this.readHolds.get();
                }

                var4 = var3.count;
                if (var4 <= 1) {
                    this.readHolds.remove();
                    if (var4 <= 0) {
                        throw this.unmatchedUnlockException();
                    }
                }

                --var3.count;
            }

            int var5;
            do {
                var5 = this.getState();
                var4 = var5 - 65536;
            } while(!this.compareAndSetState(var5, var4));

            return var4 == 0;
        }
        
 //锁状态减为0之后,判断头结点是否为空,如果不为空,且结点的等待状态为-1,则需uppark下一个结点,改变节点的状态。如果没有下一个结点,头结点保持为空
 private void doReleaseShared() {
        while(true) {
            AbstractQueuedSynchronizer.Node var1 = this.head;
            if (var1 != null && var1 != this.tail) {
                int var2 = var1.waitStatus;
                if (var2 == -1) {
                //把节点状态从-1改为0,如果改变失败,就重试。之后,唤醒头结点的后继结点。(唤醒在parkAndCheckInterrupt()中阻塞的线程)
                    if (!compareAndSetWaitStatus(var1, -1, 0)) {
                        continue;
                    }

                    this.unparkSuccessor(var1);
                } else if (var2 == 0 && !compareAndSetWaitStatus(var1, 0, -3)) {
                    continue;
                }
            }

            if (var1 == this.head) {
                return;
            }
        }
    }
//打断阻塞在这的线程。在tryAcquireShared中获得锁,由于不是写锁,也没超上限,所以将节点状态由0改为1,返回1。setHeadAndPropagate 将坐在的节点设置为头结点,
private void doAcquireShared(int var1) {
        AbstractQueuedSynchronizer.Node var2 = this.addWaiter(AbstractQueuedSynchronizer.Node.SHARED);
        boolean var3 = true;

        try {
            boolean var4 = false;

            while(true) {
                AbstractQueuedSynchronizer.Node var5 = var2.predecessor();
                if (var5 == this.head) {
                    int var6 = this.tryAcquireShared(var1);
                    if (var6 >= 0) {
                        this.setHeadAndPropagate(var2, var6);
                        var5.next = null;
                        if (var4) {
                            selfInterrupt();
                        }

                        var3 = false;
                        return;
                    }
                }

                if (shouldParkAfterFailedAcquire(var5, var2) && this.parkAndCheckInterrupt()) {
                    var4 = true;
                }
            }
        } finally {
            if (var3) {
                this.cancelAcquire(var2);
            }
//如果头结点的下一个节点为空,或者也是共享状态,那么继续释放共享锁。这也是可以读读并发的原因
 private void setHeadAndPropagate(AbstractQueuedSynchronizer.Node var1, int var2) {
        AbstractQueuedSynchronizer.Node var3 = this.head;
        this.setHead(var1);
        if (var2 > 0 || var3 == null || var3.waitStatus < 0 || (var3 = this.head) == null || var3.waitStatus < 0) {
            AbstractQueuedSynchronizer.Node var4 = var1.next;
            if (var4 == null || var4.isShared()) {
                this.doReleaseShared();
            }
        }

    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值