在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