读写锁。
关于读写锁的底层原理
-
读写锁比较适合于读多写少的并发场景,它的实现主要是将int类型变量的32位拆分为高低16位,高16用于表示读锁,低16位用于表示写锁,对于读锁属于共享锁,写锁属于独占锁,所以写锁的重入次数只要通过移位操作就能计算,但是读锁却不能直接这么做,所以采用的是第一个线程用firstReader变量记录,而重入次数使用firstReaderCount记录,而其他线程通过ThreadLocal的线程变量副本HoldCounter来存,其中tid表示线程,count表示重入次数
-
对于读写锁的使用,要注意的是写锁能降级为读锁,但是读锁不能升级为写锁,而一般的用法是先加写锁,然后加读锁,释放写锁,再释放读锁,在写锁中加读锁主要是为了避免脏读的情况
-
对于读写锁属于悲观读,因为读的时候不能写,所以JDKs的时候引入了stampedLock来保证乐观读,采用CAS的方式来避免加锁阻塞线程,但是操作起来比较复杂,一般不推荐使用
1.读写锁是怎么计算重入次数的
通过state的高 16位表示读锁重入次数 低16位表示写锁重入次数。一个int类型是4个字节(byte)32位(bit) 当state位0时为 0000000000000000|0000000000000000 前16位位高位表示读锁 后16位位低位表示写锁。当state为0000000000000001|0000000000000000 时表示有一个线程占有读锁。当state为0000000000000000|0000000000000010则表示一个线程重入了两次写锁。而读锁重入依靠额外开启线程单独计数。
写锁的持有线程标识继承自 AQS,写锁(独占锁)的持有线程标识存储在父类 AbstractQueuedSynchronizer的 exclusiveOwnerThread字段中。

刚接触读写锁时知道是在读多写少的业务中使用但是疑惑这个锁的实际意义在哪,既然读锁能共享那和没加锁有什么区别。
后面经过询问AI了解到了他的真正作用,有相同疑问的可以看一下。
我:这段代码是我对读写锁的一些疑惑。既然读锁并不互斥那加读锁和不加锁有什么区别呢?我了解到读写锁是在一些读多写少的业务场景中进行使用,但是我不知道他具体应该怎么应用才能展示出他的优势或者灵活性。
1 import java.util.concurrent.locks.ReentrantReadWriteLock; 2 3 public class Main { 4 5 6 static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 7 static final ReentrantReadWriteLock.ReadLock readLock = lock.readLock(); 8 static final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); 9 static final Object synLock = new Object(); 10 11 public static void main(String[] args) { 12 13 // readWriteLock(); 14 syn(); 15 } 16 17 private static void readWriteLock() { 18 Runnable task = () -> { 19 readLock.lock(); 20 try { 21 System.out.println(Thread.currentThread().getName() + "begin Reading" + System.currentTimeMillis()); 22 } finally { 23 readLock.unlock(); 24 } 25 writeLock.lock(); 26 try { 27 Thread.sleep(3000); 28 System.out.println(Thread.currentThread().getName() + "begin Writing" + System.currentTimeMillis()); 29 } catch (InterruptedException e) { 30 throw new RuntimeException(e); 31 } finally { 32 writeLock.unlock(); 33 } 34 }; 35 new Thread(task, "Thread-1").start(); 36 new Thread(task, "Thread-2").start(); 37 new Thread(task, "Thread-3").start(); 38 } 39 40 public static void syn() { 41 Runnable task = () -> { 42 System.out.println(Thread.currentThread().getName() + "begin Reading" + System.currentTimeMillis()); 43 44 synchronized (synLock) { 45 try { 46 Thread.sleep(3000); 47 } catch (InterruptedException e) { 48 throw new RuntimeException(e); 49 } 50 System.out.println(Thread.currentThread().getName() + "begin Writing" + System.currentTimeMillis()); 51 } 52 53 54 }; 55 new Thread(task, "Thread-1").start(); 56 new Thread(task, "Thread-2").start(); 57 new Thread(task, "Thread-3").start(); 58 } 59 }
AI:
读写锁的核心优势在于读读并发、读写互斥、写写互斥。让我们通过一个更合适的示例来展示它的优势:
1. 问题分析
在你的代码中,readWriteLock()方法存在几个问题:
-
每个线程先读后写,这实际上是"写多读少"的场景
-
没有区分真正的"读线程"和"写线程"
-
没有展示出多个线程同时读的优势
1 import java.util.concurrent.locks.ReentrantReadWriteLock; 2 import java.util.ArrayList; 3 import java.util.List; 4 import java.util.Random; 5 6 public class CacheSystem { 7 // 模拟一个缓存系统 8 private static List<String> cache = new ArrayList<>(); 9 private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 10 private static final ReentrantReadWriteLock.ReadLock readLock = lock.readLock(); 11 private static final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); 12 13 public static void main(String[] args) throws InterruptedException { 14 // 初始化一些数据 15 for (int i = 0; i < 5; i++) { 16 cache.add("Data-" + i); 17 } 18 19 System.out.println("=== 测试读写锁(读多写少场景)==="); 20 21 // 创建多个读线程 22 for (int i = 0; i < 5; i++) { 23 new Thread(new Reader(), "Reader-" + i).start(); 24 } 25 26 // 创建一个写线程 27 new Thread(new Writer(), "Writer-1").start(); 28 29 Thread.sleep(1000); 30 31 System.out.println("\n=== 测试synchronized(对比)==="); 32 33 // 同样的场景用synchronized 34 for (int i = 0; i < 5; i++) { 35 new Thread(new SynchronizedReader(), "SyncReader-" + i).start(); 36 } 37 new Thread(new SynchronizedWriter(), "SyncWriter-1").start(); 38 } 39 40 // 使用读写锁的读操作 41 static class Reader implements Runnable { 42 @Override 43 public void run() { 44 for (int i = 0; i < 3; i++) { 45 readLock.lock(); 46 try { 47 long start = System.currentTimeMillis(); 48 // 模拟读操作耗时 49 Thread.sleep(100); 50 System.out.println(Thread.currentThread().getName() + 51 " 读取缓存,大小:" + cache.size() + 52 ",耗时:" + (System.currentTimeMillis() - start) + "ms"); 53 } catch (InterruptedException e) { 54 e.printStackTrace(); 55 } finally { 56 readLock.unlock(); 57 } 58 } 59 } 60 } 61 62 // 使用读写锁的写操作 63 static class Writer implements Runnable { 64 @Override 65 public void run() { 66 for (int i = 0; i < 3; i++) { 67 writeLock.lock(); 68 try { 69 long start = System.currentTimeMillis(); 70 // 模拟写操作耗时更长 71 Thread.sleep(500); 72 cache.add("NewData-" + System.currentTimeMillis()); 73 System.out.println(Thread.currentThread().getName() + 74 " 写入缓存,新大小:" + cache.size() + 75 ",耗时:" + (System.currentTimeMillis() - start) + "ms"); 76 } catch (InterruptedException e) { 77 e.printStackTrace(); 78 } finally { 79 writeLock.unlock(); 80 } 81 } 82 } 83 } 84 85 // 使用synchronized的读操作 86 static class SynchronizedReader implements Runnable { 87 @Override 88 public void run() { 89 for (int i = 0; i < 3; i++) { 90 synchronized (cache) { 91 try { 92 long start = System.currentTimeMillis(); 93 Thread.sleep(100); 94 System.out.println(Thread.currentThread().getName() + 95 " 读取缓存,大小:" + cache.size() + 96 ",耗时:" + (System.currentTimeMillis() - start) + "ms"); 97 } catch (InterruptedException e) { 98 e.printStackTrace(); 99 } 100 } 101 } 102 } 103 } 104 105 // 使用synchronized的写操作 106 static class SynchronizedWriter implements Runnable { 107 @Override 108 public void run() { 109 for (int i = 0; i < 3; i++) { 110 synchronized (cache) { 111 try { 112 long start = System.currentTimeMillis(); 113 Thread.sleep(500); 114 cache.add("NewData-" + System.currentTimeMillis()); 115 System.out.println(Thread.currentThread().getName() + 116 " 写入缓存,新大小:" + cache.size() + 117 ",耗时:" + (System.currentTimeMillis() - start) + "ms"); 118 } catch (InterruptedException e) { 119 e.printStackTrace(); 120 } 121 } 122 } 123 } 124 } 125 }
运行上述代码,你会看到:
使用读写锁时:
-
多个读线程可以同时执行(读读并发)
-
只有写线程在执行时,其他线程才会阻塞
-
总体执行时间较短
使用synchronized时:
-
即使只是读操作,所有线程也必须串行执行
-
读线程会相互阻塞
-
总体执行时间较长
. 关键点总结
-
适用场景:读操作 >> 写操作(如缓存、配置管理、元数据查询)
-
读锁的作用:
-
不是为了防止并发读(多个读本来就可以并发)
-
而是为了在读取时阻止写入,保证读取的一致性
-
同时也阻止了写入时被读取,防止读到中间状态
-
-
优势体现:当有大量读线程和少量写线程时,吞吐量显著提升
在你的原始代码中,每个线程都是"先读后写",这实际上变成了"写多"的情况,没有体现出读写锁的优势。真正的优势在于多个线程可以同时读,而写操作相对较少的场景。
通过上面AI的示例可以知道,只要读锁和写锁是从同一个ReentrantReadWriteLock对象中获取的,那么即使读锁和写锁在不同的任务中,读锁被持有时依然可以阻塞写锁。
但是SynchronizedReader锁如果要实现相同的效果就必须在读操作也添加锁才行,如果只在写任务添加锁那么读操作时依然可能会有并发安全问题导致脏读。
1 import java.util.Map; 2 import java.util.concurrent.ConcurrentHashMap; 3 import java.util.concurrent.locks.ReentrantReadWriteLock; 4 import java.util.concurrent.TimeUnit; 5 6 /** 7 * 通过读写锁保证MySQL与Redis数据一致性 8 * 实现读写分离,提高读性能的同时保证数据一致性 9 */ 10 public class CacheConsistencyWithRWLock { 11 12 // 模拟MySQL数据库 13 static class MySQLDataSource { 14 private final Map<String, String> database = new ConcurrentHashMap<>(); 15 16 public MySQLDataSource() { 17 // 初始化一些数据 18 database.put("user:1", "Alice"); 19 database.put("user:2", "Bob"); 20 database.put("user:3", "Charlie"); 21 } 22 23 public String query(String key) { 24 // 模拟数据库查询 25 try { 26 TimeUnit.MILLISECONDS.sleep(50); // 模拟数据库查询延迟 27 } catch (InterruptedException e) { 28 Thread.currentThread().interrupt(); 29 } 30 return database.get(key); 31 } 32 33 public boolean update(String key, String value) { 34 // 模拟数据库更新 35 try { 36 TimeUnit.MILLISECONDS.sleep(100); // 模拟数据库更新延迟 37 } catch (InterruptedException e) { 38 Thread.currentThread().interrupt(); 39 return false; 40 } 41 database.put(key, value); 42 return true; 43 } 44 45 public boolean delete(String key) { 46 try { 47 TimeUnit.MILLISECONDS.sleep(80); 48 } catch (InterruptedException e) { 49 Thread.currentThread().interrupt(); 50 return false; 51 } 52 return database.remove(key) != null; 53 } 54 } 55 56 // 模拟Redis缓存 57 static class RedisCache { 58 private final Map<String, String> cache = new ConcurrentHashMap<>(); 59 60 public String get(String key) { 61 // 模拟缓存读取 62 try { 63 TimeUnit.MILLISECONDS.sleep(10); // 模拟缓存读取延迟 64 } catch (InterruptedException e) { 65 Thread.currentThread().interrupt(); 66 } 67 return cache.get(key); 68 } 69 70 public void set(String key, String value) { 71 // 模拟缓存写入 72 try { 73 TimeUnit.MILLISECONDS.sleep(20); 74 } catch (InterruptedException e) { 75 Thread.currentThread().interrupt(); 76 } 77 cache.put(key, value); 78 } 79 80 public void delete(String key) { 81 try { 82 TimeUnit.MILLISECONDS.sleep(15); 83 } catch (InterruptedException e) { 84 Thread.currentThread().interrupt(); 85 } 86 cache.remove(key); 87 } 88 89 public boolean exists(String key) { 90 return cache.containsKey(key); 91 } 92 } 93 94 // 数据服务类,使用读写锁保证一致性 95 static class DataService { 96 private final MySQLDataSource mysql; 97 private final RedisCache redis; 98 99 // 使用细粒度锁:为每个key创建独立的读写锁 100 // 实际生产环境可以使用ConcurrentHashMap + 锁分段 101 private final Map<String, ReentrantReadWriteLock> keyLocks = new ConcurrentHashMap<>(); 102 103 public DataService(MySQLDataSource mysql, RedisCache redis) { 104 this.mysql = mysql; 105 this.redis = redis; 106 } 107 108 // 获取key对应的锁 109 private ReentrantReadWriteLock getLockForKey(String key) { 110 return keyLocks.computeIfAbsent(key, k -> new ReentrantReadWriteLock()); 111 } 112 113 /** 114 * 读取数据 - 先读缓存,缓存没有则读数据库并回填缓存 115 * 使用读锁:允许多个线程同时读 116 */ 117 public String readData(String key) { 118 ReentrantReadWriteLock.ReadLock readLock = getLockForKey(key).readLock(); 119 120 try { 121 readLock.lock(); // 获取读锁 122 123 // 1. 先尝试从缓存读取 124 String value = redis.get(key); 125 if (value != null) { 126 System.out.println(Thread.currentThread().getName() + 127 " [缓存命中] key: " + key + ", value: " + value); 128 return value; 129 } 130 131 // 2. 缓存未命中,从数据库读取 132 value = mysql.query(key); 133 if (value != null) { 134 // 3. 回填到缓存(使用异步或同步,这里用同步简化) 135 redis.set(key, value); 136 System.out.println(Thread.currentThread().getName() + 137 " [缓存回填] key: " + key + ", value: " + value); 138 } else { 139 System.out.println(Thread.currentThread().getName() + 140 " [数据不存在] key: " + key); 141 } 142 143 return value; 144 } finally { 145 readLock.unlock(); // 释放读锁 146 } 147 } 148 149 /** 150 * 更新数据 - 先更新数据库,再删除缓存(Cache-Aside Pattern) 151 * 使用写锁:保证更新时互斥 152 */ 153 public boolean updateData(String key, String newValue) { 154 ReentrantReadWriteLock.WriteLock writeLock = getLockForKey(key).writeLock(); 155 156 try { 157 writeLock.lock(); // 获取写锁 158 159 System.out.println(Thread.currentThread().getName() + 160 " [开始更新] key: " + key + ", newValue: " + newValue); 161 162 // 1. 先更新数据库 163 boolean success = mysql.update(key, newValue); 164 165 if (success) { 166 // 2. 删除缓存(而不是更新缓存) 167 redis.delete(key); 168 169 System.out.println(Thread.currentThread().getName() + 170 " [更新完成] 数据库已更新,缓存已删除"); 171 172 // 注意:这里采用删除而不是更新缓存,是为了避免复杂的并发问题 173 // 下次读取时会自动从数据库加载最新数据到缓存 174 } 175 176 return success; 177 } finally { 178 writeLock.unlock(); // 释放写锁 179 } 180 } 181 182 /** 183 * 删除数据 - 先删除数据库,再删除缓存 184 */ 185 public boolean deleteData(String key) { 186 ReentrantReadWriteLock.WriteLock writeLock = getLockForKey(key).writeLock(); 187 188 try { 189 writeLock.lock(); // 获取写锁 190 191 System.out.println(Thread.currentThread().getName() + 192 " [开始删除] key: " + key); 193 194 // 1. 先删除数据库 195 boolean success = mysql.delete(key); 196 197 if (success) { 198 // 2. 删除缓存 199 redis.delete(key); 200 201 System.out.println(Thread.currentThread().getName() + 202 " [删除完成] 数据库和缓存都已删除"); 203 } 204 205 return success; 206 } finally { 207 writeLock.unlock(); // 释放写锁 208 } 209 } 210 211 /** 212 * 批量读取 - 演示多个key的读取如何加锁 213 */ 214 public void batchRead(String... keys) { 215 // 对多个key的读取,需要按固定顺序加锁,避免死锁 216 // 这里简化为对每个key独立加读锁 217 for (String key : keys) { 218 readData(key); 219 } 220 } 221 } 222 223 // 测试程序 224 public static void main(String[] args) throws InterruptedException { 225 MySQLDataSource mysql = new MySQLDataSource(); 226 RedisCache redis = new RedisCache(); 227 DataService dataService = new DataService(mysql, redis); 228 229 System.out.println("=== 测试1: 多个线程同时读同一个key(读读并发)==="); 230 231 // 多个线程同时读取同一个数据 232 Runnable readTask = () -> { 233 for (int i = 0; i < 3; i++) { 234 dataService.readData("user:1"); 235 try { 236 Thread.sleep(100); 237 } catch (InterruptedException e) { 238 Thread.currentThread().interrupt(); 239 } 240 } 241 }; 242 243 Thread reader1 = new Thread(readTask, "读线程-1"); 244 Thread reader2 = new Thread(readTask, "读线程-2"); 245 Thread reader3 = new Thread(readTask, "读线程-3"); 246 247 reader1.start(); 248 reader2.start(); 249 reader3.start(); 250 251 reader1.join(); 252 reader2.join(); 253 reader3.join(); 254 255 System.out.println("\n=== 测试2: 读写并发(读写互斥)==="); 256 257 // 创建一个读线程和一个写线程同时操作同一个key 258 Thread readThread = new Thread(() -> { 259 for (int i = 0; i < 5; i++) { 260 dataService.readData("user:2"); 261 try { 262 Thread.sleep(200); 263 } catch (InterruptedException e) { 264 Thread.currentThread().interrupt(); 265 } 266 } 267 }, "读线程"); 268 269 Thread writeThread = new Thread(() -> { 270 try { 271 Thread.sleep(100); // 让读线程先开始 272 dataService.updateData("user:2", "Bob-Updated"); 273 } catch (InterruptedException e) { 274 Thread.currentThread().interrupt(); 275 } 276 }, "写线程"); 277 278 readThread.start(); 279 writeThread.start(); 280 281 readThread.join(); 282 writeThread.join(); 283 284 System.out.println("\n=== 测试3: 写写互斥 ==="); 285 286 // 两个写线程同时更新同一个key 287 Runnable writeTask = () -> { 288 for (int i = 0; i < 2; i++) { 289 dataService.updateData("user:3", Thread.currentThread().getName() + "-v" + i); 290 try { 291 Thread.sleep(300); 292 } catch (InterruptedException e) { 293 Thread.currentThread().interrupt(); 294 } 295 } 296 }; 297 298 Thread writer1 = new Thread(writeTask, "写线程-A"); 299 Thread writer2 = new Thread(writeTask, "写线程-B"); 300 301 writer1.start(); 302 writer2.start(); 303 304 writer1.join(); 305 writer2.join(); 306 307 System.out.println("\n=== 测试4: 不同key的读写不互相影响 ==="); 308 309 // 测试不同key的并发操作 310 Thread t1 = new Thread(() -> { 311 dataService.readData("user:1"); 312 }, "操作user:1的线程"); 313 314 Thread t2 = new Thread(() -> { 315 dataService.updateData("user:2", "NewValue"); 316 }, "操作user:2的线程"); 317 318 Thread t3 = new Thread(() -> { 319 dataService.readData("user:3"); 320 }, "操作user:3的线程"); 321 322 t1.start(); 323 t2.start(); 324 t3.start(); 325 326 t1.join(); 327 t2.join(); 328 t3.join(); 329 } 330 }

浙公网安备 33010602011771号