本文概览:在ReetrantReadWriteLock中定义了一个具体同步器Sync,ReetrantReadWriteLock中读锁和写锁的lock和unlock操作都是通过该同步器来实现。
1 读写锁ReadWriteLock介绍
ReentrantLock 实现了线程抢夺同步器的互斥操作,同一个时刻只能有一个线程来使用同步器,在这种情况下任何“读/读”,“写/读”,“写/写”操作都不能同时发生,降低了了吞吐量。为了优化上面的问题引入了读写锁ReadWriteLock来提高吞吐量。
1、ReadWriteLock的使用场景
读写锁使用的场景是一个共享资源被大量读取操作,而只有少量的写操作(增、删、改)。
2、ReadWriteLock的抢夺资源机制如下:
(1)当同步器被读线程占用
- 同步器能够被多个读线程访问;
- 只有所有读锁线程释放,才可以获取写锁
(2)当同步器被写线程占用
对于这个线程的读锁可以获取这个同步器,但是对于其他读线程无法获取到同步器。
2 ReadWriteLock的同步器Sync
在ReadWriteLock中定义了一个具体同步器Sync(支持公平和非公平两种机制)。这个同步器实现了共享模式和互斥两个模式(对于ReetrantLock中同步器只实现了排他模式):
- 实现共享模式,通过实现tryAcquireShared和tryReleasShared的接口。
- 实现互斥模式,通过实现AQS同步器的排他模式,实现了tryAcquire和tryReelase接口
2.1 共享模式实现
可以通过这个同步器Sync共享模式的acquireShard和releaseShard接口来实现读锁ReadLock:
- lock,通过同步器Sync的acquireShard实现。
- unlock,通过同步器Sync的releaseShard实现
对于争夺锁的逻辑tryAcquiredShard的进一步分析
(1)情况1 存在写线程占用同步器
- 如果为当前读线程和同步器的线程一样,那么此时就可以获取同步器
- 如果当前读线程和同步器的线程不一样,则不能获取同步器
(2)情况2 只有读线程占用同步器
- 如果readShouldBlock返回true,如果此时当前读线程的重入次数为0,不能获取。如果重入次数大于0,则可以获取。
- 如果readShouldBlock返回false,那么此时可以获取到同步器。
(3)情况3 没有线程占用同步器,(此时类似于 互斥模式下下进程,当状态值为0时,此时进行判断shouldBlock)
- 如果readShouldBlock返回true,不能获取同步器
- 如果readShouldBlock返回false,能够获取同步器
tryAcquireShard(int acquires) 共享模式下的抢夺资源策略如下:
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 |
protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); int c = getState(); //1.第一步 如果是写线程占用同步器,并且同步器当前线程不为当前线程,返回false // 注意:对于当前线程占有写锁时,当前线程仍然可以获取读锁,但是其他线程不可以 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; int r = sharedCount(c); //2.第二步 readerShouldBlock()判断为true之后,可以直接CAS来进行抢占同步器 if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { if (r == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != current.getId()) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return 1; } // 3.第三步 处理readShouldBlock为true和CAS失败的情况 return fullTryAcquireShared(current); } |
2.1.1.1 fullTryAcquireShard
对于上面tryAcquireShard流程第二步中readeShouldBlock为true或者CAS为失败的情况,此时提供了两个功能
(1)对于tryAcquireShard中CAS失败的情况处理。
当有多个读线程同时进行获取的情况下,对于ReetrantLock和writeLock,处理CAS失败方法就是将这个线程节点添加到SYNC队列中;对于读锁ReadLock,是可以同时执行的,所以需要有一个while循环重新尝试获取一次。
(2)对于readShouldBlock返回true处理
此时并没有直接进行阻塞这个读线程,而是判断当前线程是否可重入,即进行如下两个判断:
- 当前读线程可重入次数为0则进行阻塞
- 这个读线程可重入的次数大于0,说明这个线程已经获取了读锁,那么需要再次获取读锁时,此时就不进行阻塞
注意:和tryAcquire相比,在tryAcquire中只要writerShouldBlock()成功就会阻塞了;而对于tryAcquireShard中readShouldBlock()返回true时还需要fullTryAcquireShard进行上面两个判断。
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 |
final int fullTryAcquireShared(Thread current) { /* * This code is in part redundant with that in * tryAcquireShared but is simpler overall by not * complicating tryAcquireShared with interactions between * retries and lazily reading hold counts. */ HoldCounter rh = null; for (;;) { int c = getState(); // 1 如果写线程占用同步器。且当前线程就是这个写线程,则不进行阻塞,否则进行阻塞 if (exclusiveCount(c) != 0) { if (getExclusiveOwnerThread() != current) return -1; // 2 readShouldBlock返回true时,还需要保证读线程可重入性。 // readShouldBlock返回true分为如下两种情况: // (1)对于非公平性的读锁:如果HEAD->NEXT节点为写线程节点,则返回true,进行阻塞;否则,不进行阻塞 // (2)对于公平性的读锁:如果如果HEAD->NEXT节点包含线程节点节点,则返回true,进行阻塞;否则,不进行阻塞 } else if (readerShouldBlock()) { //2.1 当前线程已经获取读锁,则不进行阻塞 if (firstReader == current) { // assert firstReaderHoldCount > 0; } else { //2.2 当前线程没有获取读锁,但是如果当前线程的可重入次数大于0,则不进行阻塞。 if (rh == null) { rh = cachedHoldCounter; if (rh == null || rh.tid != current.getId()) { rh = readHolds.get(); if (rh.count == 0) readHolds.remove(); } } if (rh.count == 0) return -1; } } if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded"); // 3 通过CAS抢占锁。如果失败,则通过whilie循环,继续执行 if (compareAndSetState(c, c + SHARED_UNIT)) { if (sharedCount(c) == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { if (rh == null) rh = cachedHoldCounter; if (rh == null || rh.tid != current.getId()) rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; cachedHoldCounter = rh; // cache for release } return 1; } } } |
2.1.1.2 共享模式下的公平性和非公平性机制
通过readerShouldBlock来实现,具体如下:
1、公平性锁
对于公平性的机制是:如果HEAD->NEXT节点包含线程节点,则此时返回true,读线程阻塞
1 2 3 4 5 6 |
static final class FairSync extends Sync { private static final long serialVersionUID = -2274990926593161451L; final boolean readerShouldBlock() { return hasQueuedPredecessors(); } } |
2、非公平性锁
对于非公平性的机制:如果HEAD->NEXT节点为写线程节点,则此时返回true,读线程进行阻塞,其他情况不阻塞。
1 2 3 4 5 6 7 8 |
static final class NonfairSync extends Sync { private static final long serialVersionUID = -8159625535654395037L; final boolean readerShouldBlock() { // 判断Head-newt节点是否为互斥的线程节点 return apparentlyFirstQueuedIsExclusive(); } } |
AQS的apparentlyFirstQueuedIsExclusive()逻辑如下:
1 2 3 4 5 6 7 8 |
final boolean apparentlyFirstQueuedIsExclusive() { Node h, s; return (h = head) != null && (s = h.next) != null && // 是否为互斥模式节点 !s.isShared() && s.thread != null; } |
3、公平性与非公平性比较
(1)共同点
两种机制都保证:写线程不出现饥饿。
(2)不同点
- 公平性,最新线程不跟当前线程抢夺资源,直接放置到同步对列后面。
- 非公平性,最新线程和当前线程抢夺资源
在ReentrantLock中tryRelase就是:(1)重新设置state(2)如果state为0就设置当前进程为null。对于读锁的tryReleaseShared操作也是从这两个方面阐述。
对于ReadLock的tryRelaseShared,不需要设置当前进程了,因为可能同时有多个读线程同时在运行,所以只需要重新设置读的可重入次数就可以了。
1 2 3 4 5 6 7 8 9 10 11 |
for (;;) { int c = getState(); //因为读的可重入次数在state的高位,所以使用c-Shared_UNIT表示C的读次数减去1 int nextc = c - SHARED_UNIT; if (compareAndSetState(c, nextc)) // Releasing the read lock has no effect on readers, // but it may allow waiting writers to proceed if // both read and write locks are now free. // 判断state的值是否为0。读锁和写锁的可重入次数都是0,则返回true return nextc == 0; } |
1、为什么这里是判断state的值是否为0,而不是判断读锁的次数为0?
tryReleaseShared与tryReleased作用是一样的:都是为了从同步队中获取一个线程来执行。对于tryReleaseShared是为了从同步队列中获取一个写线程。
对于一个读线程而言,即使读的可重入次数不为0,也能由可扩展性来执行同步对列中的读线程,所以tryReleaseShared是为了验证是否可以从同步对列中读取一个写线程,那么就需要判断写锁的次数是否为0,而且为了让写锁能够执行还需要读锁为0,所以此时就需要判断state为0.
2、tryReleaShard和trRelease比较:
(1)读锁执行unlock是为了从同步队列中获取写线程,因为读线程可以通过延展性执行队列中读线程。所以在tryReleaseShared需要判断state是为0,即读和写的可重入次数都为0
(2)写锁执行unlock是为了从同步队列中获取一个线程,这个线程包括读或者写两种。所以在tryRelease时,只需要判断写重入次数为0就可以了
2.2 互斥模式
可以通过同步器的互斥模式acquire和release两个接口来实现写锁WriteLock:
- lock,通过同步器Sync的acquired实现。
- unlock,通过同步器Sync的release实现。
2.2.1 tryAcquire 获取同步器
写锁抢夺资源同步器的逻辑为:
(1)存在写线程时,如果是当前线程进行执行,否则阻塞。
(2)在没有线程占用同步器的时候,进行如下判断
- 如果writeShouldLock为ture时,进行阻塞。
- 如果writeShouldLock为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 |
protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); // 1.第一步 在同步器被占用情况下 if (c != 0) { // 实现两个判断: //(1)如果是读锁占用,就返回false; // (2)如果是写锁占用,如果是当前线程就继续,否则不是当前线程, 就返回false.[这里是实现可重入性] if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires); return true; } // 2. 第二步 同步器没有占用进行抢占: //(1)执行writeShouldBlock,分为公平同步器和非公平两种机制。 // (2)执行CAS,抢占同步器 if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; // 3.第三步 如果可以抢占到同步器设置当前线程 setExclusiveOwnerThread(current); return true; } |
2.2.1.1 互斥模式下公平性和非公平性
通过writerShouldBlock来实现,具体如下:
1、公平性机制
对于公平性的机制,如果HEAD->NEXT节点包含线程节点,则此时返回true,读写程阻塞
1 2 3 4 5 6 |
static final class FairSync extends Sync { private static final long serialVersionUID = -2274990926593161451L; final boolean writerShouldBlock() { return hasQueuedPredecessors(); } } |
2、非公平性机制
对于非公平机制,不进行任何判断,直接可以准许线程进行抢夺资源同步器
1 2 3 4 5 6 |
static final class NonfairSync extends Sync { private static final long serialVersionUID = -8159625535654395037L; final boolean writerShouldBlock() { return false; // writers can always barge } } |
2.2.2 tryRelease
对于WriteLock的tryRelease和ReentrentLock的一样,都需要:(1)重新设置写的可重入次数(2)如果写锁的可重入次数为0就设置当前线程为null。
1 2 3 4 5 6 7 8 9 10 11 |
protected final boolean tryRelease(int releases) { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int nextc = getState() - releases; // 判断写锁可重入次数为0 boolean free = exclusiveCount(nextc) == 0; if (free) setExclusiveOwnerThread(null); setState(nextc); return free; } |
1、为什么tryRelease判断是写锁的可重入次数为0,不是写锁和读锁的的次数都为0?
这是因为如下情况:当一个写线程中添加了读锁,此时写锁释放,读锁还保存着,此时如果同步队列中第一个线程节点为读线程,那么此时就可以直接运行;但是,如果tryRelease是根据写锁和读锁次数都为0时,才成立,那么此时这个读线程就无法执行了。如下代码
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 |
public class WriteLockTest { class TaskRunner { ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(false); public void runReader() { rwLock.readLock().lock(); try { System.out.println("runReader执行读操作"); } finally { rwLock.readLock().unlock(); } } public void runWriteAndRead() { rwLock.writeLock().lock(); try { Thread.sleep(3000); System.out.println("runWriteAndRead执行完写操作,读锁"); rwLock.readLock().lock(); } catch (Exception e) { } finally { System.out.println("runWriteAndRead写线程解锁"); rwLock.writeLock().unlock(); } try { System.out.println("runWriteAndRead执行读操作...30s"); //执行读操作 Thread.sleep(3000); System.out.println("runWriteAndRead执行读操作完成"); } catch (Exception e) { } finally { rwLock.readLock().unlock(); } } } public TaskRunner getTaskRunner() { return new TaskRunner(); } public static void main(String[] args) { WriteLockTest wlTest = new WriteLockTest(); final TaskRunner taskRunner = wlTest.getTaskRunner(); Thread tWR = new Thread(new Runnable() { @Override public void run() { taskRunner.runWriteAndRead(); } }); Thread tR = new Thread(new Runnable() { @Override public void run() { taskRunner.runReader(); } }); tWR.start(); tR.start(); } } |
执行结果为
1 2 3 4 5 |
runWriteAndRead执行完写操作,读锁 runWriteAndRead写线程解锁 runWriteAndRead执行读操作...3s runReader执行读操作 runWriteAndRead执行读操作完成 |
3 ReadLock
3.1 lock
通过同步器Sync的共享模式接口acqurieShard来实现。
1 2 3 |
public void lock() { sync.acquireShared(1); } |
3.2 unlock
通过同步器Sync的共享模式接口reaseShard来实现。
1 2 3 |
public void unlock() { sync.releaseShared(1); } |
4 WriteLock
4.1 Lock
通过同步器互斥模式接口acquire来实现:
1 2 3 |
public void lock() { sync.acquire(1); } |
4.2 Unlock
通过同步器互斥模式接口releas来实现:
1 2 3 |
public void unlock() { sync.release(1); } |
5 读写锁应用实例
对于读锁和写锁简单的应用—读操作使用读锁,写操作使用写锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class RWDictionary { private final Map<String, Data> m = new TreeMap<String, Data>(); private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock(); //1. 读操作使用读锁 public Data get(String key) { r.lock(); try { return m.get(key); } finally { r.unlock(); } } //2. 写操作使用读锁 public Data put(String key, Data value) { w.lock(); try { return m.put(key, value); } finally { w.unlock(); } } } |
6 相关问题
6.1 读锁使用理解
1 2 3 |
Q: 当有读锁在执行时,是否再过来读线程,也能获取到锁呢? A: 不一定 |
1、以非公平同步器来说明
在使用非公平同步器时,读锁和写锁的区别在阻塞对列为空,且阻塞对列HEAD->NEXT节点不为写节点的时候才能体现出来:
在阻塞对列为空时,如果读线程占用同步器,此时又有读线程过来,则就可以获取同步器。但是如果一旦阻塞对列不为空且HEAD->NEXT节点为写节点,那么此时过来的读线程就要阻塞,就和写锁的tryAcquire的效果一样了 。
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 |
public class WriteLockTest { class TaskRunner{ ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(false); public void runReader(){ rwLock.readLock().lock(); try { System.out.println("reader"); while (true){ } }finally { rwLock.readLock().unlock(); } } public void runWrite(){ rwLock.writeLock().lock(); try{ System.out.println("writer"); }finally { rwLock.writeLock().unlock(); } } } public TaskRunner getTaskRunner(){ return new TaskRunner(); } public static void main(String[] args){ WriteLockTest wlTest = new WriteLockTest(); final TaskRunner taskRunner = wlTest.getTaskRunner(); Thread tR1 = new Thread(new Runnable() { @Override public void run() { taskRunner.runReader(); } }); Thread tW = new Thread(new Runnable() { @Override public void run() { taskRunner.runWrite(); } }); Thread tR2 = new Thread(new Runnable() { @Override public void run() { taskRunner.runReader(); } }); tR1.start(); tW.start(); tR2.start(); } } |
发现执行结果就是
1 |
reader |
这是因为在执行tR2.start进行获取锁时,发现在阻塞对列中发下了线程节点tw了,由于在tryAcquireShared的逻辑就是如果Head->netx节点为写节点,且当前读线程的可重入次数为0,此时就应该阻塞。所以tR2被阻塞。
此时如果将如下代码:
1 2 3 |
tR1.start(); tW.start(); tR2.start(); |
修改代码为如下,此时就可以执行tR2了,因为此时阻塞对列中Head->next没有节点。
1 2 3 |
tR1.start(); tR2.start(); tW.start(); |
所以,此时执行结果为:
1 2 |
reader reader |
2. 对于公平性同步器来言
在使用公平同步器时,读锁和写锁的区别只有在阻塞对列为空的时候才能体现出来:
在阻塞对列为空时,如果读线程占用同步器,此时又有读线程过来,则就可以获取同步器。但是如果一旦阻塞对列不为空,那么此时过来的读线程就要阻塞,就和写锁的tryAcquire的效果一样了 。
6.2 ReetrantReadWriteLock和ReetrantLock的比较
1、比较WriteLock 和 ReentrantLock
两者可以是一样的
2、比较WriteLock和ReadLock
(1) 使用非公平同步器
使用非公平同步器时,读锁和写锁的区别在阻塞对列中的Head->Next节点为读节点的时候或者为空的时候才能体现出来:
在阻塞对列为空时或者Head->next节点不是写线程节点,如果此时读线程占用同步器,那么又有度线程过来,则就可以获取同步器。但是如果一旦阻塞对列的Head->next节点为写节点,那么此时过来的读线程就要阻塞,就和写锁的tryAcquire的效果一样了 。
(2)使用公平同步器
使用公平同步器时,读锁和写锁的区别只有在阻塞对列为空的时候才能体现出来:
在阻塞对列为空时,如果读线程占用同步器,此时又有读线程过来,则就可以获取同步器。但是如果一旦阻塞对列不为空,那么此时过来的读线程就要阻塞,就和写锁的tryAcquire的效果一样了 。
6.3 如何来表示读写锁的同步器是否被占用
1、state
对于ReentrantLock 里,状态值表示重入计数。那么对于读写锁ReetrantReadWriteLock,如何表示读锁和写锁的可重入性和记录相应的可重入的次数?将状态分为高位和低位两部分:
- 高位表示读锁的所有线程的可重入的数的总和
- 低位表示写锁的可重入个数。
总结如下:
- 在ReetrantLock中,我们可以将状态看成获取同步器的标识:如果状态为0时,表示没有ReetrantLock没有获取锁;如果状态大于0时,表示有线程已经占有,状态的值表示的是重入的次数。
- 在读写ReetrantReadWriteLock锁中,状态的高位和地位都为0,则表示没有任何线程占用,如果高位非0,表示读线程占用,状态值表示的是所有读线程的可重入次数总和;如果低位不为0表示写线程占用,状态值表示重入次数。
可以通过如下两个函数从状态中获取高位和低位的值,sharedCount不为 0 表示分配了读锁,exclusiveCount 不为 0 表示分配了写锁。
1 2 3 4 5 |
/** Returns the number of shared holds represented in count */ static int sharedCount(int c) { return c >>> SHARED_SHIFT; } /** Returns the number of exclusive holds represented in count */ static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } |
2、对于读线程的可重入次数,引入了HoldCounter
1 2 3 4 5 6 |
static final class HoldCounter { int count = 0; // 使用id而不是引用是为了避免保留垃圾。注意这是个常量。 final long tid = Thread.currentThread().getId(); } |
在记录读线程的可重入数,有如下几个变量,
1 2 3 4 5 6 7 8 9 |
// 1. 第一个获读锁的线程和可重入次数 private transient Thread firstReader = null; private transient int firstReaderHoldCount; // 2. 最新的获取锁的线程和可重入信息 private transient HoldCounter cachedHoldCounter; // 3.记录当前线程的可重入次数。 private transient ThreadLocalHoldCounter readHolds; |
查看这些变量的引用位置,发现就是在getReadHoldCount中使用,这个函数作用就是获取当前读线程的可重入次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
final int getReadHoldCount() { if (getReadLockCount() == 0) return 0; Thread current = Thread.currentThread(); if (firstReader == current) return firstReaderHoldCount; HoldCounter rh = cachedHoldCounter; if (rh != null && rh.tid == current.getId()) return rh.count; int count = readHolds.get().count; if (count == 0) readHolds.remove(); return count; } |
6.4 为什么读锁没有Condition?
首先要明白condition引入的目的:就是为了实现两个互斥的进程顺序的执行。
对于读锁,可能多个读线程同时运行,此时就无法保证进程顺序了,所以就没有condition的用法了。
因为在一个时刻,对于读锁,可能同时又多个线程占用同步器,所以此时就无法进行设置 setExclusiveOwnerThread。
6.6 读锁的”延展性“是否与公平性有关系
当一个wirter1正在执行时,此时依次进入了读线程R1、R2、R3,W2,R4,R5,这三个线程被加入到同步对列中,如果此时writer1执行完了,那么此时就会执行R1,在R1的setHead中运行R2,在R2的setHead中运行R3,直到 W2时停止。
1 |
无论公平性读锁还是非公平性读锁,上面执行过程是一样的。 |
如下代码,设置为公平性和非公平性,执行结果都是一样的
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 |
public class WriteLockTest { class TaskRunner { ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true); public void runReader() { rwLock.readLock().lock(); try { System.out.println("reader"); while (true) { } } finally { rwLock.readLock().unlock(); } } public void runWrite() { rwLock.writeLock().lock(); try { System.out.println("writer"); //争取一定时间可以将读线程放到同步队列中 Thread.sleep(5000); } catch (Exception e) { } finally { rwLock.writeLock().unlock(); } } } public TaskRunner getTaskRunner() { return new TaskRunner(); } public static void main(String[] args) { WriteLockTest wlTest = new WriteLockTest(); final TaskRunner taskRunner = wlTest.getTaskRunner(); Thread tW = new Thread(new Runnable() { @Override public void run() { taskRunner.runWrite(); } }); Thread tR1 = new Thread(new Runnable() { @Override public void run() { System.out.println("t1"); taskRunner.runReader(); } }); Thread tR2 = new Thread(new Runnable() { @Override public void run() { System.out.println("t2"); taskRunner.runReader(); } }); Thread tR3 = new Thread(new Runnable() { @Override public void run() { System.out.println("t3"); taskRunner.runReader(); } }); tW.start(); try{ Thread.sleep(2000); } catch (Exception exception){ } tR1.start(); tR2.start(); tR3.start(); } } |
执行结果为:
1 2 3 4 5 6 7 |
writer t1 t2 t3 reader reader reader |
(全文完)