1. 前言
如果使用 synchronized 关键字,那么无论是读请求,还是写请求,都是串行化,十分影响性能,而读请求本身是可以并行的。互联网中大部分请求都是读请求,如果能允许并行读,写的时候使用串行,效率会更高。
2. 读写分离锁
有一点值得注意的是,只要涉及到写操作,就一定要加锁,串行化。
2.1 先定义ReadWriteLock类
首先我们定义几个变量,用来记录处于各个状态的线程数量。如下所示:
1 | // 正在读的线程数 |
其中值得注意的是 preferWriteFlag ,这个变量是为了满足我们自定义读写偏向性的需求。比如,如果我们更希望写优先,那么设置 preferWriteFlag 为 true 。在读加锁的时候,判断如果该值为 true,并且有部分写线程正处于等待中,那么读线程先 wait ,让写线程先执行。
我示例中的代码是写优先,当等待写线程数大于 1 时,读线程先进入等待,让写线程先执行。具体代码如2.1.1。
2.1.1 读加锁
在读加锁的时候,要判断是否有线程正在写,如果有,则要先等待。如果没有,则正在读线程数加 1。代码如下:
1 | public synchronized void readLock() throws InterruptedException { |
2.1.2 读解锁
解锁比较简单,正在读线程数减 1 ,然后唤醒所有线程。
1 | public synchronized void readUnlock() { |
2.1.3 写加锁
写加锁和读加锁类似,只不过判断条件不同。如果有正在读的线程或者正在写的线程,则等待。
1 | public synchronized void writeLock() throws InterruptedException { |
2.1.4 写解锁
和读解锁类似。
1 | public synchronized void writeUnlock() { |
2.1.5 提供设置preferWriteFlag的构造函数
我默认是写优先,但是提供构造函数可以修改。构造函数如下:
1 | public ReadWriteLock() { |
如构造函数代码所示,默认空构造函数设置为 true ,提供非空构造函数修改 preferWriteFlag 的值。
2.2 写共享数据类
读写锁的目标就是为了同时操作同一个共享数据,所以我们定义一个共享数据类。
我们定义共享数据为字符数组,然后引入读写锁类,提供读方法和写方法即可。具体代码如下:
1 | public class ShareData { |
代码比较简单,主要是 read 和 write 两个方法,每个方法执行都是先加锁,再解锁。为了能够看到测试效果,执行中线程会 sleep 一定的时间(如果不 sleep,控制台打印太快,看不清)。
注意: read 方法读的数据时副本。构造方法中设置了默认的数据为 ##### 。
2.3 写读线程和写线程类
无论读线程类还是写线程类,都要引入共享数据类作为变量,写线程类还要定义数据变量。具体代码如下:
1 | public class ReadThread extends Thread { |
1 | public class WriteThread extends Thread { |
2.4 测试类
在测试类中读线程多一些,写线程少一些,也比较符合实际情况。
1 | public class ReadWriteTest { |
最终执行结果如下:
Thread-8 read #################### from share data
Thread-7 read #################### from share data
Thread-3 read #################### from share data
Thread-2 read #################### from share data
Thread-5 read #################### from share data
Thread-4 read #################### from share data
Thread-6 read #################### from share data
Thread-1 read #################### from share data
Thread-0 read #################### from share data
write a to share data
write h to share data
Thread-0 read hhhhhhhhhhhhhhhhhhhh from share data
Thread-2 read hhhhhhhhhhhhhhhhhhhh from share data
Thread-6 read hhhhhhhhhhhhhhhhhhhh from share data
Thread-1 read hhhhhhhhhhhhhhhhhhhh from share data
Thread-4 read hhhhhhhhhhhhhhhhhhhh from share data
Thread-5 read hhhhhhhhhhhhhhhhhhhh from share data
Thread-3 read hhhhhhhhhhhhhhhhhhhh from share data
write i to share data
Thread-3 read iiiiiiiiiiiiiiiiiiii from share data
Thread-1 read iiiiiiiiiiiiiiiiiiii from share data
Thread-4 read iiiiiiiiiiiiiiiiiiii from share data
Thread-5 read iiiiiiiiiiiiiiiiiiii from share data
write b to share data
Thread-4 read bbbbbbbbbbbbbbbbbbbb from share data
Thread-5 read bbbbbbbbbbbbbbbbbbbb from share data
分布还是比较均匀的。
3. JDK锁对比
synchronized : 随着 JDK 版本不断迭代,效率逐渐提高;
StampedLock:JDK1.8 引入的一个锁,有兴趣可以自己了解下;
ReadWriteLock:要正确使用,读多写少性能好。
有文章对这三种锁在各种情况下进行性能的对比,综合情况下,StampedLock 表现最优。所以,为图方便,可以无脑使用 StampedLock 。