本文将提供Java中的ReadWriteLock和ReentrantReadWriteLock的示例。JDK 1.5中已经引入了ReadWriteLock和ReentrantReadWriteLock。ReentrantReadWriteLock是ReadWriteLock接口的实现,而ReadWriteLock扩展了Lock接口。ReentrantReadWriteLock是具有可重入性的ReadWriteLock的实现。ReentrantReadWriteLock具有关联的读写锁,可以重新获取这些锁。在本文,我们将通过完整的示例讨论ReadWriteLock和ReentrantReadWriteLock。
Lock
JDK 1.5中引入了java.util.concurrent.locks.Lock接口。Lock可以代替使用同步方法,并将有助于更有效的锁定系统。Lock在多线程环境中用于共享资源。Lock作用的方式是,任何线程必须必须首先获得锁才能访问受锁保护的共享资源。一次只有一个线程可以获取锁,一旦其工作完成,它将为队列中其他线程解锁资源。ReadWriteLock是扩展的接口Lock。
ReadWriteLock
JDK 1.5中引入了java.util.concurrent.locks.ReadWriteLock接口。ReadWriteLock是用于读取和写入操作的一对锁。如果没有写锁定请求,则多个线程可以同时获取读锁定请求。如果线程获得了对资源的写锁,则任何线程都无法获得对该资源的其他读或写锁。ReadWriteLock在读操作比写操作更频繁的情况下,效率更高,因为可以由多个线程同时为共享资源获取读锁定。
ReentrantReadWriteLock
JDK 1.5中引入了java.util.concurrent.locks.ReentrantReadWriteLock类。ReentrantReadWriteLock是的实现ReadWriteLock。我们将讨论ReentrantReadWriteLock的一些主要属性。
Acquisition order [获取顺序]
ReentrantReadWriteLock可以使用公平和非公平模式。默认是不公平的。当以非公平初始化时,读锁和写锁的获取的顺序是不确定的。非公平锁主张竞争获取,可能会延缓一个或多个读或写线程,但是会比公平锁有更高的吞吐量。当以公平模式初始化时,线程将会以队列的顺序获取锁。当当前线程释放锁后,等待时间最长的写锁线程就会被分配写锁;或者有一组读线程组等待时间比写线程长,那么这组读线程组将会被分配读锁。
Reentrancy [重入]
什么是可重入锁,不可重入锁呢?”重入”字面意思已经很明显了,就是可以重新进入。可重入锁,就是说一个线程在获取某个锁后,还可以继续获取该锁,即允许一个线程多次获取同一个锁。比如synchronized内置锁就是可重入的,如果A类有2个synchornized方法method1和method2,那么method1调用method2是允许的。显然重入锁给编程带来了极大的方便。假如内置锁不是可重入的,那么导致的问题是:1个类的synchornized方法不能调用本类其他synchornized方法,也不能调用父类中的synchornized方法。与内置锁对应,JDK提供的显示锁ReentrantLock也是可以重入的。
Lock downgrading [锁降级]
ReentrantReadWriteLock可以从写锁降级为读锁。这意味着,如果线程获得了写锁,则可以将其锁从写锁降级为读锁。顺序为:首先获取写锁,执行写操作,然后获取读锁,然后解锁写锁,然后在读操作之后最终解锁读锁。从读锁升级到写锁是不行的。
代码示例
package juc; import java.util.Calendar; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.ReentrantReadWriteLock; public class TestReadWriteLock { static final ReentrantReadWriteLock globalLock = new ReentrantReadWriteLock(); /** * 下面的测试代码会产生死锁,因为同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock是不支持的。 */ public static void testReadToWrite() { ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock(); rtLock.readLock().lock(); System.out.println("get read lock."); rtLock.writeLock().lock(); System.out.println("waiting"); } /** * ReentrantReadWriteLock支持锁降级,下面代码不会产生死锁。这段代码虽然不会导致死锁,但没有正确的释放锁。 * 从写锁降级成读锁,并不会自动释放当前线程获取的写锁,仍然需要显示的释放,否则别的线程永远也获取不到写锁。 */ public static void testWriteToRead() { ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock(); rtLock.writeLock().lock(); System.out.println("get write lock"); rtLock.readLock().lock(); System.out.println("get read lock"); } public static void read() { globalLock.readLock().lock(); boolean isWrite = globalLock.isWriteLocked(); if (!isWrite) { System.out.println("read lock"); } try { try { String threadName = Thread.currentThread().getName(); System.out.println(threadName + " : begin read."); Thread.sleep(1000 * 5); System.out.println(threadName + " : end read."); } catch (InterruptedException e) { e.printStackTrace(); } } finally { globalLock.readLock().unlock(); } } public static void write() { globalLock.writeLock().lock(); boolean isWrite = globalLock.isWriteLocked(); if (isWrite) { System.out.println("write lock"); } try { try { String threadName = Thread.currentThread().getName(); System.out.println(threadName + " : begin write."); Thread.sleep(1000 * 5); System.out.println(threadName + " : end write."); } catch (InterruptedException e) { e.printStackTrace(); } } finally { globalLock.writeLock().unlock(); } } /** * 读写锁的实现必须确保写操作对读操作的内存影响。换句话说,一个获得了读锁的线程必须能看到前一个释放的写锁所更新的内容,读写锁之间为互斥。 * 读锁使用共享模式;写锁使用独占模式,换句话说,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的 */ public static void mock() { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 20; i++) { executorService.submit(new Runnable() { @Override public void run() { read(); } }); if (i % 5 == 0) { executorService.submit(new Runnable() { @Override public void run() { write(); } }); } } } public static void mockApp() { final int threadCount = 2; final ExecutorService exService = Executors.newFixedThreadPool(threadCount); final ScoreBoard scoreBoard = new ScoreBoard(); exService.execute(new ScoreUpdateThread(scoreBoard)); exService.execute(new ScoreHealthThread(scoreBoard)); exService.shutdown(); } public static void main(String[] args) { // 测试读锁升级到写锁 // testReadToWrite(); // 测试写锁升级到读锁 // testWriteToRead(); // 测试读写互斥,写锁共享 // mock(); // 完整案例演示 // mockApp(); } } class ScoreBoard { private boolean scoreUpdated = false; private int score = 0; String health = "Not Available"; final ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock(); public String getMatchHealth() { rrwl.readLock().lock(); if (scoreUpdated) { rrwl.readLock().unlock(); rrwl.writeLock().lock(); try { if (scoreUpdated) { score = fetchScore(); scoreUpdated = false; } rrwl.readLock().lock(); } finally { rrwl.writeLock().unlock(); } } try { if (score % 2 == 0) { health = "Bad Score : "+score; } else { health = "Good Score : "+score; } } finally { rrwl.readLock().unlock(); } return health; } public void updateScore() { try { rrwl.writeLock().lock(); // perform more task here scoreUpdated = true; } finally { rrwl.writeLock().unlock(); } } private int fetchScore() { Calendar calender = Calendar.getInstance(); return calender.get(Calendar.MILLISECOND); } } class ScoreHealthThread implements Runnable { private ScoreBoard scoreBoard; public ScoreHealthThread(ScoreBoard scoreTable) { this.scoreBoard = scoreTable; } @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("Match Health: " + scoreBoard.getMatchHealth()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } class ScoreUpdateThread implements Runnable { private ScoreBoard scoreBoard; public ScoreUpdateThread(ScoreBoard scoreTable) { this.scoreBoard = scoreTable; } @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("Score Updated."); scoreBoard.updateScore(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
总结
1.Java并发库中ReetrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性
2.ReetrantReadWriteLock读写锁的效率明显高于synchronized关键字
3.ReetrantReadWriteLock读写锁的实现中,读锁使用共享模式;写锁使用独占模式,换句话说,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的
4.ReetrantReadWriteLock读写锁的实现中,需要注意的,当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁