ReadWriteLock是JDK5中提供的读写分离锁.读写锁可以有效的减少锁竞争,提升系统的吞吐量。
读写锁的访问约束情况
读 | 写 | |
---|---|---|
读 | 非阻塞 | 阻塞 |
写 | 阻塞 | 阻塞 |
读读之间不阻塞,读和写之间相互阻塞,写和写之间阻塞。系统中如果读操作比写操作多,则可以考虑使用读写锁增加系统的吞吐量,提高系统的性能。
读写锁允许多个读线程同时读,使得多个读线程真正并行。但是由于数据的完整性,所以读和写之间,写和写之间需要相互等待和持有锁。读操作占用的时间越长,则使用读写锁的优势越明显。
样例比较读写锁和重入锁的性能差距
测试电脑 配置
2.7 GHz 双核Intel Core i5, RAM 8 GB 1867 MHz DDR3
openjdk 16.0.1 OpenJDK 64-Bit Server VM Homebrew (build 16.0.1+0, mixed mode, sharing)
public class ReadWriteLockDemo {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = reentrantReadWriteLock.readLock();
private static Lock writeLock = reentrantReadWriteLock.writeLock();
private static ReentrantLock reentrantLock = new ReentrantLock();
private static CountDownLatch countDownLatch = new CountDownLatch(20); //多线程控制
private static int readThreadNum = 20;
private static int writeThreadNum = 2;
private static CountDownLatch reentrantDownLatch = new CountDownLatch(20);
private int value;
public Object readSomething(Lock lock) throws InterruptedException {
try {
lock.lock();
Thread.sleep(1000); //模拟耗时操作
return value;
} finally {
lock.unlock();
}
}
public Object writeSomething(Lock lock, int newValue) throws InterruptedException {
try {
lock.lock();
Thread.sleep(1000); //模拟耗时操作
this.value = newValue;
return this.value;
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReadWriteLockDemo demo = new ReadWriteLockDemo();
Runnable readTask = () -> {
try {
demo.readSomething(readLock);
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Runnable writeTask = () -> {
try {
demo.writeSomething(writeLock, new Random().nextInt());
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Runnable readTask2 = () -> {
try {
demo.readSomething(reentrantLock);
reentrantDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Runnable writeTask2 = () -> {
try {
demo.writeSomething(reentrantLock, new Random().nextInt());
reentrantDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
long begin = System.currentTimeMillis();
for (int i = 0; i < readThreadNum; i++) new Thread(readTask).start();
for (int i = 0; i < writeThreadNum; i++) new Thread(writeTask).start();
countDownLatch.await(); //等待子线程全部完成工作
long end = System.currentTimeMillis();
System.out.println("[readWriteLock cost] " + (end - begin) + "ms");
long begin2 = System.currentTimeMillis();
for (int i = 0; i < readThreadNum; i++) new Thread(readTask2).start();
for (int i = 0; i < writeThreadNum; i++) new Thread(writeTask2).start();
reentrantDownLatch.await(); //等待子线程全部完成工作
long end2 = System.currentTimeMillis();
System.out.println("[reentrantLock cost] " + (end2 - begin2) + "ms");
}
}
输出值
两者的输出值,在笔者电脑上为如下所示:
[readWriteLock cost] 1011ms
[reentrantLock cost] 20069ms
总结
性能相差20倍多,不同的电脑和jdk版本表现有所不同。如果将读线程的数量或者耗时增加,则读写锁的性能优势会更加明显。在读多的场景下可以采用读写锁提升性能。