在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