天天看點

Java并發程式設計一ReentrantReadWriteLock初使用

推薦:​​Java并發程式設計彙總​​

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​

​​屬性為​

​true​

​​,​

​sync​

​​會被指派為​

​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​

​​屬性為​

​false​

​​,​

​sync​

​​會被指派為​

​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得到了讀鎖,正在讀取
更新會帶來阻塞      
Java并發程式設計一ReentrantReadWriteLock初使用

線程在鎖更新過程中被一直阻塞着,這也很明顯驗證了​

​寫鎖是可以降級為讀鎖的,而讀鎖是不可以更新為寫鎖的​

​。為什麼不可以呢?這樣設計是有原因的,因為鎖更新容易引起死鎖。