Java同步技术(八)

版权声明

作者联系并注明出处http://blog.csdn.net/Iangao/archive/2008/11/08/3256407.aspx

3.2 读写问题

3.2.1 读写问题分析

当某一资源有可能被多个线程同时访问,而访问又可以分为读写两种时,将会遇到读写问题这一讨论。下面我们详细分析一下其中的主要问题。

1) 读写锁

当一个资源执行读操作时,它的值是不会改变的,也就是说,我们可以允许同时一间有多个读线程同时对资源执行读操作,但这段期间是不允许对资源进行修改的。然而写操作会给把资源的数据进行一些修改,这样在修改期间就不允许有其它的读写操作与之并行,所以我们可以得出同一时间只允许有一个线程对资源执行写操作。我们把开始读到读结束这段期间对资源的访问称为读临界区(读锁),把开始写到写结束这段时间对资源的访问称为写临界区(写锁).

通过上面的分析我们可以对读写问题做如下总结:

对存在读写问题的资源加以控制需要两个锁协同工作来完成,我们把这样一组锁称为读写锁同一时间,只允许读锁和写锁中的一个锁定对资源的访问。读锁锁定期间,不允许有写操作线程对资源访问,但允许其他读操作线程对资源访问。写操作期间,其他的无论读写操作线程都不允许对资源进行问题

2) 读写优先

一面我们讨论一下读写的执行优先级问题。读锁期间,当系统到来一个读请求时,如果有写操作在等待,是否还读它继续进入临界区呢?如果进入的话,就有可能导致写操作长时间无法得到响应,然而通常我们会更希望写操作更快的得到响应。另一种情况是当写操作结束后,是更优先处理写等待还是更优先处理读等待呢?根据不同的需求我们可能会做出不同的决策。下面我们只针对如下一种常见策略详细分析读写问题的解决:

在有写进程执行的情况下,后续读写操作全部阻塞.在有读进程执行的情况下,后续写操作全部阻塞,读操作的阻塞与否取决于是否存在写阻塞(参看下一规则)在有写阻塞进程存在的情况下,我们阻塞后续读进程的并发操作,优先处理写进程。当写操作结束后,优先唤醒写进程,如果没有写阻塞,那么我们唤醒读进程

3.2.2 读写问题的简单实现

1. 简单实现

/*** 简单读写锁* @author iangao*/public class SimpleReadWriteLock { Mutex resource=new Mutex(); // 资源锁 Condition readers=new Condition(resource); // 读锁 Condition writers=new Condition(resource); // 写锁 int readerCount=0; // 并发读数 boolean writting=false; // 写标识 /** * 开始读, 如果有写等待,就先不处理读了 */ public void readLock() throws InterruptedException{ resource.p(); // [在写||有写等待], 则等待 if(writting||writers.queue()>0) readers.await(); while(writting) readers.await(); // 读计数[加并发] readerCount++; // 唤醒下条并发读[队列中](写后可能会有多条读等待) readers.signal(); resource.v(); } /** * 读结束 */ public void readUnlock() throws InterruptedException{ resource.p(); // 读计数[减并发] readerCount–; // 全(并发)读完, 唤醒写 // a. 有写等待(被读阻塞),有读等待(被写等待阻塞) // b. 有写等待(被读阻塞),无读等待 // c. 无写等待,无读等待 if(readerCount==0) writers.signal(); resource.v(); } /** * 开始写 */ public void writeLock() throws InterruptedException{ resource.p(); // [在写 || 在读] , 则在writers队列等待… while(writting||readerCount!=0) writers.await(); // 开始写 writting=true; resource.v(); } /** * 写结束 */ public void writeUnlock() throws InterruptedException{ resource.p(); // 写结束 writting=false; // 优先唤醒writers队列 // a. 有写等待(被写阻塞), 有读等待(被写阻塞) // b. 有写等待(被写阻塞), 无读等待 // c. 无写等待, 无读等待 if(writers.queue()>0) writers.signal(); else readers.signal(); resource.v(); }}

3.2.3 读写问题的测试

/*** 简单读写锁测试类 * @author iangao*/public class SimpleReaderWriterLockTest { public static void main(String[] args){ new ThreadsTest(){ /** * 共享资源类 * @author iangao */ class Resource { SimpleReadWriteLock lock=new SimpleReadWriteLock(); private int seq=0; /** * 读资源 */ void read(long mills) throws InterruptedException{ int id=seq++; try{ output(“[“+id+”]:准备读…”); lock.readLock(); // 随机模拟读一段时间 output(“[“+id+”]:读”, random(0,(int)mills)); }finally{ output(“[“+id+”]:读完”); lock.readUnlock(); } } /** * 写资源 */ void write(long mills) throws InterruptedException{ int id=seq++; try{ output(“[“+id+”]:准备写…”); lock.writeLock(); // 随机模拟写一段时间 output(“[“+id+”]:写”,random(500,(int)mills)); }finally{ output(“[“+id+”]:写完”); lock.writeUnlock(); } } } Resource resource=new Resource(); // 创建共享资源 /** * 读线程操作 */ void reader(long mills) throws InterruptedException { for(int i=0; i<3; i++){ resource.read(mills); sleep(100); } } /** * 写线程操作 */ void writer(long mills) throws InterruptedException { for(int i=0; i<3; i++){ resource.write(mills); sleep(100); } } /** * 启动五组读写线程 */ void runInThread1() throws InterruptedException{ name(“读1”); reader(1000); // 1秒内的读 } void runInThread2() throws InterruptedException{ name(“读2”); reader(2000); // 2秒内的读 } void runInThread3() throws InterruptedException{ name(“读3”); sleep(1000); reader(10); // 0.01秒内的读 } void runInThread4() throws InterruptedException{ name(“写1”); writer(1000); // 1秒内的写 } void runInThread5() throws InterruptedException{ name(“写2”); writer(2000); // 2秒内的写 } }.execute(5); }}

执行结果:

[读1]: [0]:准备读…[读2]: [1]:准备读…[写1]: [2]:准备写…[写2]: [3]:准备写…[读2]: [1]:读… (0.684秒)[读1]: [0]:读… (0.909秒)[读2]: [1]:读完[读2]: [4]:准备读…[读1]: [0]:读完[写1]: [2]:写… (1.316秒)[读3]: [5]:准备读…[读1]: [6]:准备读…[写1]: [2]:写完[写2]: [3]:写… (0.72秒)[写1]: [7]:准备写…[写2]: [3]:写完[写1]: [7]:写… (0.851秒)[写2]: [8]:准备写…[写1]: [7]:写完[写2]: [8]:写… (0.79秒)[写1]: [9]:准备写…[写2]: [8]:写完[写1]: [9]:写… (1.483秒)[写2]: [10]:准备写…[写1]: [9]:写完[写2]: [10]:写… (0.931秒)[写2]: [10]:写完[读2]: [4]:读… (1.974秒)[读3]: [5]:读… (0.0090秒)[读1]: [6]:读… (0.35秒)[读3]: [5]:读完[读3]: [11]:准备读…[读3]: [11]:读… (0.0060秒)[读3]: [11]:读完[读3]: [12]:准备读…[读3]: [12]:读… (0.0080秒)[读3]: [12]:读完[读1]: [6]:读完[读1]: [13]:准备读…[读1]: [13]:读… (0.023秒)[读1]: [13]:读完[读2]: [4]:读完[读2]: [14]:准备读…[读2]: [14]:读… (0.817秒)[读2]: [14]:读完

3.2.4 小节

由以上两节的分析我们可以看出,根据不同的读写策略我们可以写出不同的读写锁.我们可以看出上面实现的只是一种最化的读写锁,如果要在读写锁的基础上再做条件变量操作是无法实现的,因为它需要读锁和写锁分别以Lock锁接口的形式程现才行.如果有这类需求的朋友根据以上述的原理可以很容易重构实现出来.在此就不再继续讨论了.

3.2.5 J2SE5.0中读写锁的实现

在J2SE5.0中我们可以调用java.util.concurrent.locks.ReadWriteLock来实现读写操作,其中分别用ReadWriteLock.getReadLock().lock()和ReadWriteLock.getReadLock().unlock()来完成读的加锁和解锁,用ReadWriteLock.getWriteLock().lock()和ReadWriteLock.getWriteLock().unlock()来完成写的加锁和解锁.

只有他的好身体,没有地方可去,只想到处流浪、人生就像一场旅行,

Java同步技术(八)

相关文章:

你感兴趣的文章:

标签云: