Java ReadWriteLock读写锁的使用

本文将提供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读写锁的实现中,需要注意的,当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁