Java多线程编程-ReentrantReadWriteLock 读写锁使用

Java多线程编程-ReentrantReadWriteLock 读写锁使用

  • ReentrantReadWriteLock读锁与读锁不互斥
  • ReentrantReadWriteLock读锁与写锁互斥
  • ReentrantReadWriteLock写锁与写锁互斥
  • ReentrantReadWriteLock写锁与读锁互斥

ReentrantLock具有完全互斥的效果,每次只能有一个线程在执行lock方法后的任务。这样可以保证实例变量的线程安全性,但是效果不是很理想。假如我们多有线程都是读操作,没有写操作的时候就不需要其他线程去等待当前线程读取完,因为没有数据的变化,这样的话性能会有相应的提升。如果写操作的时候,就需要等待即可。我们可以使用 ReentrantReadWriteLock 类进行处理,ReentrantReadWriteLock 中有两个锁,一个是读取操作相关的锁,一个是写操作相关的锁。读操作的锁是共享锁,写操作是排他锁,这个是一个很正常的,不然多个线程编辑一个文件不就乱套了,哈哈。
所以可以得出以下结论:
1.多个读锁之间不互斥
2.读锁和写锁互斥
3.写锁和写锁互斥
4.写锁和读锁互斥

ReentrantReadWriteLock读锁与读锁不互斥

验证读锁和读锁不互斥:

public class ReentranReadWirteLockMain1 {

    public static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    testReadFun();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Stream.of("thread01", "thread02", "thread03").forEach(s -> new Thread(runnable, s).start());
    }

    public static void testReadFun() throws InterruptedException {
        reentrantReadWriteLock.readLock().lock();
        try {
            System.out.println("current thread name:" + Thread.currentThread().getName() + " get read lock");
            Thread.sleep(5000);
            System.out.println("current thread name:" + Thread.currentThread().getName() + " read lock end");
        } finally {
            reentrantReadWriteLock.readLock().unlock();
        }
    }
}

运行结果:
Java多线程编程-ReentrantReadWriteLock 读写锁使用_第1张图片
多个线程不会等待其他线程释放锁,先输出前三行,之后5秒之后输出后三行。说明启动的时候都可以获得读锁,不会等待。提升读取的效率,不会出现线程等待问题。

ReentrantReadWriteLock读锁与写锁互斥

验证一下一个线程读操作和另外一个写操作会不会互斥

public class ReentranReadWirteLockMain3 {

    public static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    testReadFun();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                try {
                    testWriteFun();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread thread = new Thread(runnable, "thread01");
        thread.start();
        Thread.sleep(1000);
        Thread thread2 = new Thread(runnable2, "thread02");
        thread2.start();

    }

    public static void testReadFun() throws InterruptedException {
        reentrantReadWriteLock.readLock().lock();
        try {
            System.out.println("current thread name:" + Thread.currentThread().getName() + " get read lock");
            System.out.println(System.currentTimeMillis());
            Thread.sleep(5000);
        } finally {
            reentrantReadWriteLock.readLock().unlock();
        }
    }

    public static void testWriteFun() throws InterruptedException {
        reentrantReadWriteLock.writeLock().lock();
        try {
            System.out.println("current thread name:" + Thread.currentThread().getName() + " get write lock");
            System.out.println(System.currentTimeMillis());
            Thread.sleep(5000);
        } finally {
            reentrantReadWriteLock.writeLock().unlock();
        }
    }
}

运行结果:
Java多线程编程-ReentrantReadWriteLock 读写锁使用_第2张图片
我们看看结果,时间相隔5秒,这是怎么回事?这个是读锁与写锁互斥导致的,不会同时又进入写操作和读操作。

ReentrantReadWriteLock写锁与写锁互斥

验证一下一个线程写操作和另外一个写操作会不会互斥

public class ReentranReadWirteLockMain2 {

    public static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    testReadFun();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Stream.of("thread01", "thread02", "thread03").forEach(s -> new Thread(runnable, s).start());
    }

    public static void testReadFun() throws InterruptedException {
        reentrantReadWriteLock.writeLock().lock();
        try {
            System.out.println("current thread name:" + Thread.currentThread().getName() + " get write lock");
            Thread.sleep(5000);
            System.out.println("current thread name:" + Thread.currentThread().getName() + " write lock end");
        } finally {
            reentrantReadWriteLock.writeLock().unlock();
        }
    }
}

运行结果:
Java多线程编程-ReentrantReadWriteLock 读写锁使用_第3张图片
有运行结果可以看出,打印的时候最开始那个线程先获取锁就等待5秒输出current thread name: 线程名 read lock end,之后再运行获取锁的操作,有打印可以看出写操作是互斥操作

ReentrantReadWriteLock写锁与读锁互斥

上面读锁和写锁互斥,其实反过来也是一样的。

Thread thread2 = new Thread(runnable2, "thread02");
thread2.start();
Thread.sleep(1000);
Thread thread = new Thread(runnable, "thread01");
thread.start();

运行结果:
Java多线程编程-ReentrantReadWriteLock 读写锁使用_第4张图片
证明写锁和读锁也是互斥的。
我们来看看读操作和写操作互斥原理:
源码:
ReentrantReadWriteLock也会有公平锁和非公平锁之分,和ReentrantLock一致,但是就是会有读锁和写锁的处理。

 //一个读锁
 private final ReentrantReadWriteLock.ReadLock readerLock;
 //一个写锁
 private final ReentrantReadWriteLock.WriteLock writerLock;

public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

读锁实现核心代码:

//以共享模式获取,忽略中断。通过首先至少调用一次tryAcquireShared并成功返回来实现。
//否则,线程将排队,并可能反复阻塞和解除阻塞,并调用tryAcquireShared直到成功。
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

tryAcquireShared()方法:

//如果另一个线程持有写锁定,则失败。 否则,此线程有资格进入锁定wrt状态,
//因此请问是否由于队列策略而应阻塞。如果不是,请尝试按CASing状态授予许可并更新计数。
//请注意,此步骤不会检查重入获取,这会推迟到完整版本,以避免在更典型的非重入情况下必须检查保留计数。 
//如果第2步失败,或者由于线程显然不符合条件或者CAS失败或计数饱和,请使用完全重试循环链接到版本。
 protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            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);
}

fullTryAcquireShared()方法:

 final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            //否则我们将持有排他锁;在此处阻塞将导致死锁。
        } else if (readerShouldBlock()) {
            // 确保我们没有重新获取读锁
            if (firstReader == current) {
                // 断言firstReaderHoldCount> 0
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            if (sharedCount(c) == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}

写锁实现核心代码:

//在独占模式下获取,忽略中断。通过至少调用一次tryAcquire并成功返回来实现
//否则,线程将排队,并可能反复阻塞和解除阻塞,并调用tryAcquire直到成功
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
    //如果读取计数为非零或写入计数为非零,并且所有者是另一个线程,则失败。 
    //如果计数将饱和,则失败。 (只有在count已经不为零时,才可能发生这种情况。)
    //否则,如果该线程是可重入获取或队列策略允许的话,则有资格进行锁定。如果是这样,请更新状态并设置所有者。
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires);
        return true;
    }
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

你可能感兴趣的:(Java多线程,Java多线程,ReadWriteLock,读写锁,Lock,锁使用)