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()来完成写的加锁和解锁.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值