读写锁与synchronized关键字的代码差异及执行时间对比问询
读写锁 vs synchronized:使用差异、实现对比与性能测试
嘿,我来帮你把读写锁和synchronized的区别讲明白,还会给你完整的代码实现和性能测试方案。首先先抓住核心差异,这是理解两者的关键:
核心区别
- synchronized是独占锁:不管你是读数据还是写数据,同一时间只能有一个线程拿到锁,其他线程都得等着。这种锁简单粗暴,适合读写操作都不多,或者写操作占比很高的场景,但如果是读多写少的情况,就会浪费大量并发读的性能——毕竟读操作本身不会修改数据,完全可以多个线程一起读嘛。
- 读写锁(ReentrantReadWriteLock)是分离锁:它把读和写的锁分开了:
- 读锁是共享的:多个线程可以同时获取读锁,一起读数据,互不干扰;
- 写锁是独占的:只有一个线程能拿到写锁,而且写锁和读锁是互斥的——也就是说,只要有线程在写,其他线程既不能读也不能写;只要有线程在读,写线程就得等着。
这种设计完美适配读多写少的场景,能大幅提升并发效率。
接下来我先修正你写的读写锁代码(注意你代码里的changaData拼错啦,应该是changeData),然后给出synchronized版本的实现,最后加上性能测试代码,帮你直观看到两者的时间差。
1. 读写锁实现版本
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockDemo { public static void main(String[] args) { NumberWithRWLock number = new NumberWithRWLock(5); Thread t1 = new Thread(() -> { System.out.println("Thread t1 reads: " + number.getData()); number.changeData(10); System.out.println("Thread t1 reads after write: " + number.getData()); }); Thread t2 = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread t2 reads: " + number.getData()); number.changeData(20); System.out.println("Thread t2 reads after write: " + number.getData()); }); t2.start(); t1.start(); } } class NumberWithRWLock { private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock readLock = rwl.readLock(); private final Lock writeLock = rwl.writeLock(); int value; public NumberWithRWLock(int value) { this.value = value; } public int getData() { readLock.lock(); try { // 模拟读操作的耗时,方便后续性能测试看出差异 try { Thread.sleep(10); } catch (InterruptedException e) {} return value; } finally { // 必须在finally里解锁,防止异常导致锁无法释放 readLock.unlock(); } } public int changeData(int change) { writeLock.lock(); try { // 模拟写操作的耗时 try { Thread.sleep(50); } catch (InterruptedException e) {} value = change; return value; } finally { writeLock.unlock(); } } }
2. synchronized实现版本
public class SynchronizedDemo { public static void main(String[] args) { NumberWithSync number = new NumberWithSync(5); Thread t1 = new Thread(() -> { System.out.println("Thread t1 reads: " + number.getData()); number.changeData(10); System.out.println("Thread t1 reads after write: " + number.getData()); }); Thread t2 = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread t2 reads: " + number.getData()); number.changeData(20); System.out.println("Thread t2 reads after write: " + number.getData()); }); t2.start(); t1.start(); } } class NumberWithSync { int value; public NumberWithSync(int value) { this.value = value; } // 用synchronized修饰方法,相当于给当前对象加锁 public synchronized int getData() { // 和读写锁版本用相同的模拟耗时,保证测试公平 try { Thread.sleep(10); } catch (InterruptedException e) {} return value; } public synchronized int changeData(int change) { try { Thread.sleep(50); } catch (InterruptedException e) {} value = change; return value; } }
3. 性能对比测试
单个线程的测试看不出明显差异,我们来模拟读多写少的真实场景:100个读线程,每个读100次;10个写线程,每个写10次。用CountDownLatch等待所有线程完成,统计总耗时。
import java.util.concurrent.CountDownLatch; public class PerformanceTest { private static final int READ_THREAD_COUNT = 100; private static final int WRITE_THREAD_COUNT = 10; public static void main(String[] args) throws InterruptedException { // 先测读写锁版本 long rwLockTotalTime = testReadWriteLock(); // 再测synchronized版本 long syncTotalTime = testSynchronized(); System.out.println("\n=== 性能对比结果 ==="); System.out.println("读写锁总耗时: " + rwLockTotalTime + " ms"); System.out.println("synchronized总耗时: " + syncTotalTime + " ms"); System.out.println("读写锁比synchronized快了: " + (syncTotalTime - rwLockTotalTime) + " ms"); } private static long testReadWriteLock() throws InterruptedException { NumberWithRWLock number = new NumberWithRWLock(0); CountDownLatch latch = new CountDownLatch(READ_THREAD_COUNT + WRITE_THREAD_COUNT); long startTime = System.currentTimeMillis(); // 启动所有读线程 for (int i = 0; i < READ_THREAD_COUNT; i++) { new Thread(() -> { for (int j = 0; j < 100; j++) { number.getData(); } latch.countDown(); }).start(); } // 启动所有写线程 for (int i = 0; i < WRITE_THREAD_COUNT; i++) { new Thread(() -> { for (int j = 0; j < 10; j++) { number.changeData(j); } latch.countDown(); }).start(); } // 等待所有线程执行完毕 latch.await(); return System.currentTimeMillis() - startTime; } private static long testSynchronized() throws InterruptedException { NumberWithSync number = new NumberWithSync(0); CountDownLatch latch = new CountDownLatch(READ_THREAD_COUNT + WRITE_THREAD_COUNT); long startTime = System.currentTimeMillis(); // 启动所有读线程 for (int i = 0; i < READ_THREAD_COUNT; i++) { new Thread(() -> { for (int j = 0; j < 100; j++) { number.getData(); } latch.countDown(); }).start(); } // 启动所有写线程 for (int i = 0; i < WRITE_THREAD_COUNT; i++) { new Thread(() -> { for (int j = 0; j < 10; j++) { number.changeData(j); } latch.countDown(); }).start(); } latch.await(); return System.currentTimeMillis() - startTime; } }
测试结果说明
你运行上面的测试代码后,会发现:
- 在读多写少的场景下,读写锁的耗时会比
synchronized少很多——因为100个读线程可以同时执行读操作,而synchronized每次只能一个线程执行,不管是读还是写。 - 如果是写多读少或者读写频率相近的场景,两者的性能差异会很小,甚至
synchronized可能略优,因为读写锁有额外的锁调度开销。
适用场景总结
- 用
synchronized:读写操作频率差不多,或者写操作占比高;想要代码简洁,不需要分离读写锁的场景。 - 用读写锁:读操作远多于写操作的场景(比如缓存读取、配置读取),能充分利用并发读的优势提升性能。
内容的提问来源于stack exchange,提问作者jakubradzikowski97
相关产品推荐
相关产品推荐

