浅谈ReadWriteLock

本文介绍了Java 5中引入的ReadWriteLock接口及其应用场景,对比了传统的synchronized关键字,详细解析了ReadWriteLock的工作原理,特别是ReentrantReadWriteLock的具体实现特性,并提供了简单的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在JDK5之前,我们要解决并发所产生的问题使用的是synchronized修饰。但是,对象的方法中一旦加入synchronized修饰,则任何时刻只能有一个线程访问synchronized修饰的方法。假设有个数据对象拥有写方法与读方法,多线程环境中要想保证数据的安全,需对该对象的读写方法都要加入 synchronized同步块。这样任何线程在写入时,其它线程无法读取与改变数据;如果有线程在读取时,其他线程也无法读取或写入。这种方式在写入远大于读取时,问题不大,而当读取远远大于写入时,会造成性能瓶颈,因为此种情况下读取操作是可以同时进行的,而加锁操作限制了数据的并发读取。

为此,Java 5平台新增了java.util.concurrent包,该包包含了许多非常有用的多线程应用类,例如ReadWriteLock,这使得开发人员不必自己封装就可以直接使用这些健壮的多线程类。

ReadWriteLock是一种常见的多线程设计模式。当多个线程同时访问同一资源时,通常,并行读取是允许的,但是,任一时刻只允许最多一个线程写入,从而保证了读写操作的完整性。下图很好地说明了ReadWriteLock的读写并发模型:

读     允许        不允许

写     不允许    不允许

当读线程远多于写线程时,使用ReadWriteLock来取代synchronized同步会显著地提高性能,因为大多数时候,并发的多个读线程不需要等待。

Java 5的ReadWriteLock接口仅定义了如何获取ReadLock和WriteLock的方法,对于具体的ReadWriteLock的实现模式并没有规定,例如,Read优先还是Write优先,是否允许在等待写锁的时候获取读锁,是否允许将一个写锁降级为读锁,等等。

Java 5自身提供的一个ReadWriteLock的实现是ReentrantReadWriteLock,该ReadWriteLock实现能满足绝大多数的多线程环境,有如下特点:

1、支持两种优先级模式,以时间顺序获取锁和以读、写交替优先获取锁的模式;

2、当获得了读锁或写锁后,还可重复获取读锁或写锁,即ReentrantLock;

3、获得写锁后还可获得读锁,但获得读锁后不可获得写锁;

4、支持将写锁降级为读锁,但反之不行;

5、支持在等待锁的过程中中断;

6、对写锁支持Condition(用于取代wait,notify和notifyAll);

7、支持锁的状态检测,但仅仅用于监控系统状态而并非同步控制;

测试ReadWriteLock的Demo如下:

package com.zlb.thread.readwritelock;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 对象的方法中一旦加入synchronized修饰,则任何时刻只能有一个线程访问synchronized修饰的方法。假设有个数据对象拥有写方法与读方法,多线程环境中要想保证数据的安全,需对该对象的读写方法都要加入
 * synchronized同步块。这样任何线程在写入时,其它线程无法读取与改变数据;如果有线程在读取时,其他线程也无法读取或写入。这种方式在写入操作远大于读操作时,问题不大,而当读取远远大于写入时,会造成性能瓶颈,因为此种情况下读取操作是可以同时进行的,而加锁操作限制了数据的并发读取。
 * 
 * ReadWriteLock解决了这个问题,当写操作时,其他线程无法读取或写入数据,而当读操作时,其它线程无法写入数据,但却可以读取数据 。
 * 
 * @Title:RreadWriteLockTest.java
 * @author: 周玲斌
 * @version: Jun 6, 2012 10:19:28 AM
 */
public class ReadWriteLockDemo {
	static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

	public static void main(String[] args) throws Exception {
		Data data = new Data();
		Worker t1 = new Worker(data, true);
		//第一种情况——两个线程同时写
		//Worker t2 = new Worker(data, true);
		//第二种情况——第一个线程为读,第二个线程为写
		Worker t2 = new Worker(data, false);
		t1.start();
		//第二种情况——第一个读的线程启动之后,等待0.1秒再启动第二个写的线程
		Thread.sleep(100);
		t2.start();
	}

	static class Worker extends Thread {
		Data data;
		boolean read;

		public Worker(Data data, boolean read) {
			this.data = data;
			this.read = read;
		}

		public void run() {
			if (read)
				data.get();
			else
				data.set();
		}
	}
	/**
	 * 数据类
	 * @Title:ReadWriteLockDemo.java
	 * @author: 周玲斌
	 * @version: Jun 6, 2012 10:26:26 AM
	 */
	static class Data {
		ReadWriteLock lock = new ReentrantReadWriteLock();
		Lock read = lock.readLock();
		Lock write = lock.writeLock();
		/**
		 * 写数据
		 * @author 周玲斌
		 * @date   Jun 6, 2012
		 */
		public void set() {
			write.lock();
			System.out.println(Thread.currentThread().hashCode()
					+ " set:begin " + sdf.format(new Date()));
			try {
				Thread.sleep(5000);
			} catch (Exception e) {

			} finally {
				System.out.println(Thread.currentThread().hashCode()
						+ " set:end " + sdf.format(new Date()));
				write.unlock();
			}

		}
		/**
		 * 读数据
		 * @return int
		 * @author 周玲斌
		 * @date   Jun 6, 2012
		 */
		public int get() {
			read.lock();
			System.out.println(Thread.currentThread().hashCode()
					+ " get :begin " + sdf.format(new Date()));
			try {
				Thread.sleep(5000);
			} catch (Exception e) {

			} finally {
				System.out.println(Thread.currentThread().hashCode()
						+ " get :end " + sdf.format(new Date()));
				read.unlock();
			}
			return 1;
		}
	}
}
//当两个线程同时读取数据的时候,打印的结果为:
//6718604 get :begin 2012-06-06 10:25:40
//30771886 get :begin 2012-06-06 10:25:40
//6718604 get :end 2012-06-06 10:25:45
//30771886 get :end 2012-06-06 10:25:45

//当一个线程先去读的时候,要等到读的线程完成之后写的线程才能启动,打印的结果为:
//6336176 get :begin 2012-06-06 10:29:47
//6336176 get :end 2012-06-06 10:29:52
//14718739 set:begin 2012-06-06 10:29:52
//14718739 set:end 2012-06-06 10:29:57
下面,我们来看看通过ReadWriteLock对缓存数据的操作

package com.zlb.thread.readwritelock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 通过ReadWriteLock对数据进行缓存操作
 * @Title:CacheDemo.java
 * @author: 周玲斌
 * @version: Jun 6, 2012 10:58:42 AM
 */
public class CacheDemo {

	/**
	 * 存储数据
	 */
	private Map<String, Object> cache = new HashMap<String, Object>();

	ReadWriteLock rwl = new ReentrantReadWriteLock();
	public Object getData(String key){
		//创建读锁
		rwl.readLock().lock();
		Object value = null;
		try{
			value = cache.get(key);
			if(value == null){
				//在获得写锁前,必须先释放读锁
				rwl.readLock().unlock();
				//开启写锁
				rwl.writeLock().lock();
				try{
					//防止多个写锁进来之后对其进行重复操作
					if(value == null){
						value = "zhoulingbin";
					}
				}finally{
					//在释放写锁前,先获得读锁:
					rwl.readLock().lock();
					rwl.writeLock().unlock();
				}
			}
		}finally{
			// 确保读锁在方法返回前被释放
			rwl.readLock().unlock();
		}
		return value;
	}
}

参考地址:http://soft.zdnet.com.cn/software_zone/2007/1015/556305.shtml


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值