Java多线程并发编程 — 读写锁 ReentrantReadWriteLock

ReentrantReadWriteLock 实现 interface ReadWriteLock:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

所以 ReentrantReadWriteLock 并非是真的一个“锁”,而是一个读锁和写锁的管理器,而真正实现锁功能的是 ReentrantReadWriteLock 的静态内部类 ReadLock 和 WriteLock。ReadLock 和 WriteLock 都是直接实现 interface Lock。

当多线程共享同一个数据源时,为了避免数据上的错乱,我们需要在读数据的时候防止写入操作,在写入的时候也不能进行读操作。怎么实现这一业务?synchronized、ReentrantLock 都可以实现,在读、写操作的方法都加上锁的保护,则可实现读写互斥。但这种做法会使并发大大折扣,例如在某一时间段只有大量读操作并没有写操作,但此时读操作也要进行排队,此时 ReentrantReadWriteLock 的价值就能发挥得淋漓尽致。

ReentrantReadWriteLock 的作用:支持同时多读,读的时候不能写,写的时候不能读。

看看具体的 DEMO1,一个支持多用户的银行账号(夫妻共用)的存款和查询的需求:
1,可多用户同时查询余额(读操作)
2,当有用户在查询余额时(读操作),该账号不能进行存取款操作(写入操作)
3,当有用户在存取款时(写入操作),该账号不能进行查询余额操作(读操作)

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class Test {

    static class User {
        int mID;
        BankAccount mBankAccount;

        public User(int ID, BankAccount bankAccount) {
            mID = ID;
            mBankAccount = bankAccount;
        }

        public void saveMoney(final int cash) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mBankAccount.saveMoney(mID, cash);
                }
            }).start();
        }

        public void checkBalance() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mBankAccount.checkBalance(mID);
                }
            }).start();
        }
    }

    /**
     * 支持多用户的银行账号(夫妻共用)
     */
    static class BankAccount {

        int mBalance;
        ReentrantReadWriteLock mReentrantReadWriteLock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.WriteLock mWriteLock = mReentrantReadWriteLock.writeLock();
        ReentrantReadWriteLock.ReadLock mReadLock = mReentrantReadWriteLock.readLock();

        public void saveMoney(int id, int cash) {
            mWriteLock.lock();
            try {
                mBalance = mBalance + cash;
                System.out.println(" 用户 " + id + " 正在进行存钱操作 ");
                Thread.sleep(2000);
                System.out.println(" 用户 " + id + " 存钱完成,存入 " + cash + " 元 ");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                mWriteLock.unlock();
            }
        }

        public void checkBalance(int id) {
            mReadLock.lock();
            try {
                System.out.println(" 用户 " + id + " 正在进行查询余额操作 ");
                Thread.sleep(2000);
                System.out.println(" 余额:" + mBalance);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                mReadLock.unlock();
            }
        }
    }

    public static void main(String[] var0) {
        BankAccount bankAccount = new BankAccount();
        User user1 = new User(1, bankAccount);
        User user2 = new User(2, bankAccount);

        user1.saveMoney(1000000);

        // 同时查询余额
        user1.checkBalance();
        user2.checkBalance();

        // 同时存款
        user1.saveMoney(2000000);
        user2.saveMoney(6000);

        user1.checkBalance();
    }
}

DEMO1 的输出:

1
2
3
4
5
6
7
8
9
10
11
12
用户 1 正在进行存钱操作
用户 1 存钱完成,存入 1000000 元
用户 1 正在进行查询余额操作
用户 2 正在进行查询余额操作
余额:1000000
余额:1000000
用户 1 正在进行存钱操作
用户 1 存钱完成,存入 2000000 元
用户 2 正在进行存钱操作
用户 2 存钱完成,存入 6000 元
用户 1 正在进行查询余额操作
余额:3006000

当两个用户同时查询余额时,可同时进行。
当两个用户同时存款时,不能同时进行,要进行队列排队。

可见,我们可以用 ReentrantReadWriteLock 进行一些读写需要互斥的一些业务上,特别适合在“大量读小量写”的业务,可以增大吞吐率。