Java并发编程一ReentrantReadWriteLock初使用

Java并发编程一ReentrantReadWriteLock初使用

ReentrantReadWriteLock是一种读写锁,从类名也可以看出来。

ReentrantReadWriteLock类有两个属性ReentrantReadWriteLock.ReadLock readerLock(代表读锁)、ReentrantReadWriteLock.WriteLock writerLock(代表写锁)。

    /** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;

ReadLock类和WriteLock类都是ReentrantReadWriteLock类的内部类,它们都实现了Lock接口。

    public static class ReadLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -5992448646407690164L;
        private final Sync sync;

        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
        
        public void lock() {
            sync.acquireShared(1);
        }

        public void lockInterruptibly() throws InterruptedException {
            sync.acquireSharedInterruptibly(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);
        }

        public Condition newCondition() {
            throw new UnsupportedOperationException();
        }

        public String toString() {
            int r = sync.getReadLockCount();
            return super.toString() +
                "[Read locks = " + r + "]";
        }
    }
   public static class WriteLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -4992448646407690164L;
        private final Sync sync;

        protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

        public void lock() {
            sync.acquire(1);
        }

        public void lockInterruptibly() throws InterruptedException {
            sync.acquireInterruptibly(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();
        }

        public String toString() {
            Thread o = sync.getOwner();
            return super.toString() + ((o == null) ?
                                       "[Unlocked]" :
                                       "[Locked by thread " + o.getName() + "]");
        }

        public int getHoldCount() {
            return sync.getWriteHoldCount();
        }
    }

我们来演示一下怎么使用ReentrantReadWriteLock

测试代码:

package lock.readwrite;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CinemaReadWrite {

    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

    private static void read() {
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "释放读锁");
            readLock.unlock();
        }
    }

    private static void write() {
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写入");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "释放写锁");
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(()->read(),"Thread1").start();
        new Thread(()->read(),"Thread2").start();
        new Thread(()->write(),"Thread3").start();
        new Thread(()->write(),"Thread4").start();
    }
}

输出:

Thread1得到了读锁,正在读取
Thread2得到了读锁,正在读取
Thread2释放读锁
Thread1释放读锁
Thread3得到了写锁,正在写入
Thread3释放写锁
Thread4得到了写锁,正在写入
Thread4释放写锁

很明显可以看出,读锁是可以被多个线程同时持有的,而写锁却不行,这也很正常,读数据并不会修改数据,所以多个线程一起持有读锁也是线程安全的,而写数据就不一样了。

规则如下:

  • 线程持有读锁,其他线程想要获取读锁是可行的。
  • 线程持有读锁,其他线程想要获取写锁是不可行的。
  • 线程持有写锁,其他线程想要获取读锁是不可行的。
  • 线程持有写锁,其他线程想要获取写锁是不可行的。

读写锁的方法就不演示了,看下面这篇博客即可。

Java并发编程一Lock和ReentrantLock初使用

fair属性

设置fair属性为true,则表明为公平锁,设置为false,则表明为非公平锁。

首先来看看设置fair属性为true的情况。

    /**
     * Creates a new {@code ReentrantReadWriteLock} with
     * the given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

fair属性为truesync会被赋值为new FairSync()

    /**
     * Fair version of Sync
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }

看看hasQueuedPredecessors()的源码。

    /**
     * Queries whether any threads have been waiting to acquire longer
     * than the current thread.
     */
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

从代码与注释中,可以很明显看出,如果有其他线程排在当前线程前面,当前线程就要等待前面的线程先获取锁,读写锁都是如此,这种获取锁的顺序,可见是公平的,先来先得。

再来看看设置fair属性为false的情况。

fair属性为falsesync会被赋值为new NonfairSync()

    /**
     * Nonfair version of Sync
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            /* As a heuristic to avoid indefinite writer starvation,
             * block if the thread that momentarily appears to be head
             * of queue, if one exists, is a waiting writer.  This is
             * only a probabilistic effect since a new reader will not
             * block if there is a waiting writer behind other enabled
             * readers that have not yet drained from the queue.
             */
            return apparentlyFirstQueuedIsExclusive();
        }
    }

writerShouldBlock()一直会返回为false,可见想要获取写锁的当前线程不需要等待前面的线程先获取锁,它是可以去抢锁的。

    /**
     * Returns {@code true} if the apparent first queued thread, if one
     * exists, is waiting in exclusive mode.  If this method returns
     * {@code true}, and the current thread is attempting to acquire in
     * shared mode (that is, this method is invoked from {@link
     * #tryAcquireShared}) then it is guaranteed that the current thread
     * is not the first queued thread.  Used only as a heuristic in
     * ReentrantReadWriteLock.
     */
    final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null &&
            (s = h.next)  != null &&
            !s.isShared()         &&
            s.thread != null;
    }
        /**
         * Returns true if node is waiting in shared mode.
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

isShared()是用来判断该结点是不是在等待共享锁,也就是读锁,所以从上面的代码我们可以知道,如果第一个结点是想要获取写锁的,那么想要获取读锁的当前线程是不能抢的,否则是可以抢的。

锁降级

写锁是可以降级为读锁的,而读锁是不可以升级为写锁的(认为写锁比读锁级别更高)。
测试代码:

package lock.readwrite;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Upgrading {

    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(
            false);
    private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
    private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

    private static void readUpgrading() {
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
            Thread.sleep(1000);
            System.out.println("升级会带来阻塞");
            writeLock.lock();
            System.out.println(Thread.currentThread().getName() + "获取到了写锁,升级成功");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "释放写锁");
            writeLock.unlock();
            System.out.println(Thread.currentThread().getName() + "释放读锁");
            readLock.unlock();
        }
    }

    private static void writeDowngrading() {
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写入");
            Thread.sleep(1000);
            readLock.lock();
            System.out.println("在不释放写锁的情况下,直接获取读锁,成功降级");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "释放读锁");
            readLock.unlock();
            System.out.println(Thread.currentThread().getName() + "释放写锁");
            writeLock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("演示降级是可行的");
        Thread thread1 = new Thread(() -> writeDowngrading(), "Thread1");
        thread1.start();
        thread1.join();
        System.out.println("------------------");
        System.out.println("演示升级是不行的");
        Thread thread2 = new Thread(() -> readUpgrading(), "Thread2");
        thread2.start();
    }
}

输出:

演示降级是可行的
Thread1得到了写锁,正在写入
在不释放写锁的情况下,直接获取读锁,成功降级
Thread1释放读锁
Thread1释放写锁
------------------
演示升级是不行的
Thread2得到了读锁,正在读取
升级会带来阻塞

在这里插入图片描述
线程在锁升级过程中被一直阻塞着,这也很明显验证了写锁是可以降级为读锁的,而读锁是不可以升级为写锁的。为什么不可以呢?这样设计是有原因的,因为锁升级容易引起死锁。

假如,现在线程A与线程B都持有了读锁,这是允许的,现在它们都想要进行锁升级,就是升级为写锁,所以线程A要等待线程B释放掉读锁才能去升级成写锁,而线程B要等待线程A释放掉读锁才能去升级成写锁,这样就造成了死锁。

而锁降级是安全的,因为写锁只能由一个线程持有,该线程进行锁降级是很自由的,没有其他线程来干扰。

发布了309 篇原创文章 · 获赞 448 · 访问量 71万+

猜你喜欢

转载自blog.csdn.net/qq_37960603/article/details/104365633