这里就JUC包中的StampedLock做相关介绍
概述
在读多写少的场景下,非常适合使用ReentrantReadWriteLock读写锁。但其也存在一定的弊端,其有可能导致写线程饥饿。为此JDK 8中提供了StampedLock类,其是一个非公平的读写锁。其与ReentrantReadWriteLock相比,不仅提供了传统意义上的悲观读锁和写锁,最大的区别是其还为读操作提供了乐观锁的方法——即所谓的乐观读锁。当然其也有弊端,无论是悲观读锁还是写锁,均不支持条件变量Condition;然后从类名也可以看到其是不可重入锁。需要注意的是,虽然一个线程可以多次获取悲观读锁,但究其原因是因为悲观读锁是共享锁。实际实践中,可以直接通过writeLock、readLock等阻塞式 或 tryWriteLock、tryReadLock等非阻塞式的方式获取锁,也可通过ReadLockView读锁视图、WriteLockView写锁视图、ReadWriteLockView读写锁视图来进行相应锁的操作
基本实践
读锁、写锁
这里就基本的悲观读锁、写锁的使用进行实践,示例如下所示
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
| @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class StampedLockTest1 {
private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
private static ExecutorService threadPool = Executors.newFixedThreadPool(10);
private static StampedLock stampedLock = new StampedLock();
private static Integer count;
@Test public void test1() { System.out.println("\n---------------------- Test 1 ----------------------");
count = 100; for(int i=1; i<5; i++) { Runnable runnable = new ReadTask("Task"+i); threadPool.execute( runnable ); }
try{ Thread.sleep( 10*1000 ); } catch (Exception e) {} }
@Test public void test2() { System.out.println("\n---------------------- Test 2 ----------------------");
count = 200; for(int i=1; i<5; i++) { Runnable runnable = new WriteTask("Task"+i); threadPool.execute( runnable ); }
try{ Thread.sleep( 10*1000 ); } catch (Exception e) {} }
@Test public void test3() { System.out.println("\n---------------------- Test 3 ----------------------");
count = 300; for(int i=1; i<9; i++) { Runnable task = null; Boolean isReadTask = RandomUtils.nextBoolean(); if( isReadTask ) { task = new ReadTask2("读任务 #"+i); } else { task = new WriteTask2("写任务 #"+i); } threadPool.execute( task ); } try{ Thread.sleep( 20*1000 ); } catch (Exception e) {} }
public static void info(String msg) { String time = formatter.format(LocalTime.now()); String log = "["+time+"] " + msg; System.out.println(log); }
@AllArgsConstructor private static class ReadTask implements Runnable {
private String taskName;
@Override public void run() { Integer localData = null; long stamp = stampedLock.readLock(); try{ info(taskName + ": 成功获取读锁, stamp: " + stamp); localData = count; } catch (Exception e) { info(taskName+"Happen Exception"); } finally { info(taskName + ": 释放读锁, stamp: "+stamp+", localData: "+localData); stampedLock.unlockRead(stamp); } } }
@AllArgsConstructor private static class WriteTask implements Runnable {
private String taskName;
@Override public void run() { long stamp = stampedLock.writeLock(); try { info(taskName + ": 成功获取写锁, stamp: " + stamp); count++; } catch (Exception e) { info(taskName+"Happen Exception"); } finally { info(taskName + ": 释放写锁, stamp: "+stamp+", count: " + count); stampedLock.unlockWrite(stamp); } } }
@AllArgsConstructor private static class ReadTask2 implements Runnable {
private String taskName;
@Override public void run() { Integer localData = null; Lock readLock = stampedLock.asReadLock(); readLock.lock(); try{ info(taskName + ": 成功获取读锁"); localData = count; } catch (Exception e) { info(taskName+"Happen Exception"); } finally { info(taskName + ": 释放读锁, localData: "+localData+"\n"); readLock.unlock(); } } }
@AllArgsConstructor private static class WriteTask2 implements Runnable {
private String taskName;
@Override public void run() { Lock writeLock = stampedLock.asWriteLock(); writeLock.lock(); try { info(taskName + ": 成功获取写锁"); count++; } catch (Exception e) { info(taskName+"Happen Exception"); } finally { info(taskName + ": 释放写锁, count: " + count +"\n"); writeLock.unlock(); } } } }
|
测试结果如下所示,符合预期
可以看到StampedLock获取锁、释放锁都需要相应的stamp值。为此也可以通过相应的视图类进行操作,如上述代码的test3所示。其相应测试结果所示。可以看到悲观读锁是一个共享锁,而写锁则是一个互斥锁
乐观读锁
可通过tryOptimisticRead获取一个stamp,即所谓的乐观读锁。然后在完成读操作后,通过validate方法对stamp进行检查。由于读过程通常是非原子性的,故需要判断是否存在其他线程在此期间获取到了写锁,对数据进行了修改。造成当前线程读取的数据状态不一致(部分为修改前的,部分为修改后的)。如果在当前线程进行读的过程中发生了修改更新,则检查结果为false。这时再获取悲观读锁进行重读。事实上,由于乐观读锁并没有锁。故其一方面不会阻塞写线程获取写锁,也不需要在结束后释放该锁。示例代码如下所示
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 87 88 89 90 91 92 93 94
| public class StampedLockTest2 {
private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
private static ExecutorService threadPool = Executors.newFixedThreadPool(10);
private static StampedLock stampedLock = new StampedLock();
private static Integer count;
@Test public void test1() { count = 500;
threadPool.execute( new ReadTask("读任务 #1",0) ); threadPool.execute( new ReadTask("读任务 #2",0) ); threadPool.execute( new ReadTask("读任务 #33",2*1000) ); threadPool.execute( new ReadTask("读任务 #44",2*1000) );
try{ Thread.sleep( 1000 ); } catch (Exception e) {} threadPool.execute( new WriteTask("写任务 #55") );
try{ Thread.sleep( 20*1000 ); } catch (Exception e) {} }
public static void info(String msg) { String time = formatter.format(LocalTime.now()); String log = "["+time+"] " + msg; System.out.println(log); }
@AllArgsConstructor private static class ReadTask implements Runnable {
private String taskName;
private Integer sleepTime;
@Override public void run() { long stamp = stampedLock.tryOptimisticRead(); info(taskName + ": 成功获取乐观读锁, stamp: "+stamp); Integer localData = count; try{ Thread.sleep(sleepTime); } catch (Exception e) {} info(taskName + ":localData: "+localData);
if( !stampedLock.validate(stamp) ) { info(taskName+": 数据被其他线程修改需重读"); stamp = stampedLock.readLock(); info(taskName + ": 成功获取读锁, stamp: " + stamp); try{ try{ Thread.sleep(500); } catch (Exception e) {} } catch (Exception e) { info(taskName+"Happen Exception"); } finally { info(taskName + ": 释放读锁, stamp: "+stamp+", count: " + count+"\n"); stampedLock.unlockRead(stamp); } } } }
@AllArgsConstructor private static class WriteTask implements Runnable {
private String taskName;
@Override public void run() { long stamp = stampedLock.writeLock(); try { info(taskName + ": 成功获取写锁, stamp: " + stamp); count++; } catch (Exception e) { info(taskName+"Happen Exception"); } finally { info(taskName + ": 释放写锁, stamp: "+stamp+", count: " + count +"\n"); stampedLock.unlockWrite(stamp); } } } }
|
测试结果如下所示
参考文献
- Java并发编程之美 翟陆续、薛宾田著