***********************************************声明******************************************************
原创作品,出自 “晓风残月xj” 博客,欢迎转载,转载时请务必注明出处(http://blog.csdn.net/xiaofengcanyuexj)。
由于各种原因,可能存在诸多不足,欢迎斧正!
*********************************************************************************************************
在进行多线程编程的过程中,线程间的同步与互斥是件需要认真考虑的关键点,而读者与写者就是线程间同步的典型例子:若干个读者在读取文章,若干个写者同时编辑文章,保证多个读者和多个写者能并发或并行(关于并行与并发的区别以及多线程编程的一些基本概念,可以参考这篇博文:多线程初步)执行。解决读者与写着的典型方法是设立一个文章缓冲区,然后多个读者与写者互斥或共同地访问该缓冲区。 写操作是排他锁(排斥读者,同样排斥其他写者),读操作是共享锁(多个读者可读,排斥写者)。当然,当读者与写者、写者与写者几乎同一时间到达时,涉及到优先级的分配,此时也是应该考虑的问题。
1、模型分析
读者与写者问题的特点在于读者不使缓冲区数量改变,并且多个读者可以同时读取缓冲区信息,而写者与写者、写者与读者只能互斥访问缓冲区的相同数据。但凡有写者的,可能会涉及读写冲突,即通常所说的数据竞争。对于相同的数据项,多个读者之间可以同时访问;对于相同的数据项,多个写者或写者与读者之间不能同时访问;对于不同的数据项,所有的读写访问是可以同时进行的。要尽可能提高效率,减少加锁范围,同时避免数据竞争引起的读写冲突。
贴段描述读者写着者问题模型的源代码,下面是对缓冲区的单个存储单元进行同步与互斥操作的。如果要对多个单元进行处理,可以讲int 型换成集合类型。
int count_Reader=0; int mutex=1; int WriterSemaphore=1; Reader { while(ture) { p( mutex ); if( 0 == count_Reader ) p( WriterSemaphore ); ++ count_Reader; v( mutex ); 进行读操作; p( mutex ); if( 1 == count_Reader ) v( WriterSemaphore ); -- count_Reader; v( mutex ); } } Writer { while(true) { p( WriterSemaphore ); 进行写操作 v( WriterSemaphore ); } }
上面引入原子操作p、v操作,来实现同步与互斥。
2、源代码
由于对Java中较为底层的Lock、Condition等类不太了解,加之Java中比C++中关键代码段(CriticalSection)、事件(Event)封装更好,所以暂无法找到更为高效的方法,只能贴出调用ReadWriteLock接口的Java代码,还望大牛指点:
/** * ReaderAndWriter.java * Copyright 2014.11.6 XuJin **/ import java.util.List; import java.util.ArrayList; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; class Artical { private static int m_nReaderCount = 0; private static int m_nWriterCount = 0; private ReadWriteLock lock = new ReentrantReadWriteLock(); private Lock readLock = lock.readLock(); private Lock writerLock = lock.writeLock(); private String m_strName; private int m_nId; public Artical(String name, int id) { m_strName = name; m_nId = id; } public String getName() { readLock.lock(); int seq = ++m_nReaderCount; System.out.println("第 " + seq + " 读者开始 "); String strRetValue; try { //读操作 strRetValue = m_strName; System.out.println(" 读者读取第 " + m_nId + " 个,内容为:" + strRetValue); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } finally { System.out.println("第 " + seq + " 读者结束 "); readLock.unlock(); } return strRetValue; } public void setName(String name) { writerLock.lock(); int seq = ++m_nWriterCount; System.out.println("第 " + seq + " 写者开始 "); try { // 写操作 m_strName = name; System.out.println(" 写者编辑第 " + m_nId + " 个,内容为:" + m_strName); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } finally { System.out.println("第 " + seq + " 写者结束 "); writerLock.unlock(); } } } class ArticalBuffer { private List<Artical> m_ArrArticalBuffer; public ArticalBuffer() { m_ArrArticalBuffer = new ArrayList<Artical>(); } public String getArtical(int id) { String retValue = null; if (id < m_ArrArticalBuffer.size()) retValue = m_ArrArticalBuffer.get(id).getName(); return retValue; } public void setArtical(int id, String name) { if (id < m_ArrArticalBuffer.size()) { { Artical art = m_ArrArticalBuffer.get(id); art.setName(name); } } } public void addArtical(String name) { m_ArrArticalBuffer.add(new Artical(name, m_ArrArticalBuffer.size())); } } class Reader extends Thread { ArticalBuffer m_ArticalBuffer; public Reader(ArticalBuffer pro) { m_ArticalBuffer = pro; } public void run() { int id = (int) (Math.random() * 4); m_ArticalBuffer.getArtical(id); } } class Writer extends Thread { ArticalBuffer m_ArticalBuffer; public Writer(ArticalBuffer pro) { m_ArticalBuffer = pro; } public void run() { int id = (int) (Math.random() * 4); String name = "XuJin"; m_ArticalBuffer.setArtical(id, name); } } public class ReaderAndWriter { public static void main(String[] agrs) { String m_strName[] = { "Alice", "Bob", "Green", "Dell", "Marry", "Jim", "John", "Kobe", "James", "Wed", "Paul" }; ArticalBuffer artBuffer = new ArticalBuffer(); for (int i = 0; i < m_strName.length; ++i) { artBuffer.addArtical(m_strName[i]); } Reader[] readerSet = new Reader[20]; for (int i = 0; i < 20; ++i) { readerSet[i] = new Reader(artBuffer); readerSet[i].start(); } Writer[] writerSet = new Writer[5]; for (int i = 0; i < 5; ++i) { writerSet[i] = new Writer(artBuffer); writerSet[i].start(); } } }
3、注意
1)、Lock替代synchronized关键字的使用,Condition 替代Object监视器方法(wait、notify 和 notifyAll)的使用;
2)、ReadWriteLock维护一对读写相关锁定,分别只读操作或写入操作。与互斥锁定相比,读-写锁定允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁定利用了这一点。从理论上讲,与互斥锁定相比,使用读-写锁定所允许的并发性增强将带来更大的性能提高。在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。因此,与互斥锁定相比,使用读-写锁定能否提升性能则取决于读写操作期间读取数据相对于修改数据的频率,以及数据的争用——即在同一时间试图对该数据执行读取或写入操作的线程数。
由于时间有限,在写博文的过程中参考过一些文献,在此表示感谢;同时鉴于水平原因,你难免有不足之处,欢迎斧正!