您好,登錄后才能下訂單哦!
Java 中怎么利用Thread實(shí)現(xiàn)讀寫同步,相信很多沒有經(jīng)驗(yàn)的人對(duì)此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。
多個(gè)讀者可以同時(shí)讀取同一個(gè)緩沖區(qū),但當(dāng)有寫者對(duì)緩沖區(qū)進(jìn)行寫操作時(shí),具有排他性質(zhì),其他的讀者都不能讀取這個(gè)緩沖區(qū),其他的寫者也不能寫這個(gè)緩沖區(qū)。
這個(gè)例子中包括四個(gè)類。
(1)讀寫資源類RWResource,包含了讀寫者的計(jì)數(shù),共享資源,還有所有的同步代碼。
(2)讀者類RunnableReader。實(shí)現(xiàn)Runnable接口;進(jìn)行讀操作。
(3)寫者類RunnableWriter。實(shí)現(xiàn)Runnable接口;進(jìn)行寫操作。
(4)測(cè)試類TestMain。生成并運(yùn)行多個(gè)寫線程和讀線程,顯示結(jié)果。
這個(gè)例子對(duì)共享資源進(jìn)行“盒式封裝”,把共享資源包含在一個(gè)“盒”內(nèi)。并把所有的同步代碼都集中在“盒”里面。讀者類和寫者類并不進(jìn)行同步處理,只是申請(qǐng)資源,然后進(jìn)行讀寫,讀寫完成之后,釋放資源。
這種方法的優(yōu)點(diǎn)是共享資源“盒”部分的代碼直觀易讀,緊湊可控,讀者類和寫者類不用關(guān)心同步問題。缺點(diǎn)是共享資源“盒”規(guī)定了嚴(yán)格的調(diào)用順序和調(diào)用規(guī)范,讀者類和寫者類必須嚴(yán)格遵守共享資源“盒”的調(diào)用規(guī)范,否則會(huì)造成線程死鎖,或者資源操作沖突。
不過,即使由讀者類和寫者類來實(shí)現(xiàn)線程同步,如果不注意,也會(huì)造成線程死鎖,或者資源操作沖突。這是線程的固有問題。:-)
下面給出這四個(gè)類的源代碼和說明。
package thread;
import java.util.List;
import java.util.ArrayList;
/**
* resource for reading and writing
*/
public class RWResource {
/**
* When readerNumber == 0, there is no one reading or writing.
* When readerNumber > 0, readerNumber means number of readers.
* When readerNumber < 0, it means that some writer is writing.
*/
private int readerNumber = 0;
/**
* the shared resource for writing or reading
*/
private List buffer = null;
public RWResource() {
buffer = new ArrayList(512);
readerNumber = 0;
}
/**
* get buffer for reading.
* should be called before reading
* @return the buffer
*/
public synchronized List getBufferForReading(){
// if some writer is writing, wait until no writer is writing
while(readerNumber < 0){
try{
this.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
// when readerNumber >= 0
readerNumber++;
return buffer;
}
/**
* should be called after reading
*/
public synchronized void finishReading(){
readerNumber--;
if(readerNumber == 0){
this.notifyAll(); // notify possible waiting writers
}
}
/**
* get buffer for writing.
* should be called before writing.
* @return the buffer
*/
public synchronized List getBufferForWriting(){
// if some writer is writing or some reader is reading, wait until no one is writing or reading
while(readerNumber != 0){
try{
this.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
// when readerNumber == 0
readerNumber--; // now readderNumber == -1.
return buffer;
}
/**
* should be called after writing
*/
public synchronized void finishWriting(){
readerNumber++; // readerNumber = -1 + 1 = 0;
// readerNumber must be 0 at this point
this.notifyAll(); // notify possible waiting writers or waiting readers
}
}
讀寫資源類RWResource提供了4個(gè)Synchronized方法,分成兩組,供讀者和寫者調(diào)用。
閱讀上面的代碼,可以看到,讀寫資源類RWResource通過readerNumber計(jì)數(shù)控制對(duì)共享資源的讀寫訪問。當(dāng)readerNumber等于0時(shí),說明資源空閑,可以讀寫;當(dāng)readerNumber大于0時(shí),說明資源正在被一些讀者讀取,其他線程可以讀,不可以寫;當(dāng)readerNumber小于0時(shí)(-1),說明資源被某個(gè)寫者占用,正在寫入,其他線程不可以讀,也不可以寫。
讀者首先調(diào)用getBufferForReading()獲取共享資源,如果readerNumber大于等于0,表示沒有寫者占用資源,讀者能夠獲取共享資源,此時(shí),readerNumber加1,表示讀者的個(gè)數(shù)增加;讀取之后,必須調(diào)用finishReading()釋放資源,此時(shí),readerNumber減1,表示讀者的個(gè)數(shù)減少。
寫者首先調(diào)用getBufferForWriting()獲取共享資源,如果readerNumber等于0,表示資源空閑,寫者能夠獲取到共享資源,此時(shí),readerNumber減1,readerNumber的值變?yōu)?1,表示資源正在被寫入;寫者寫完資源之后,必須調(diào)用,必須調(diào)用finishWriting()釋放資源,此時(shí),readerNumber加1,readerNumber的值變?yōu)?,回到空閑狀態(tài)。
另外,還請(qǐng)留意讀寫資源類RWResource代碼里面的wait()和notifyAll()調(diào)用。
讀者在readerNumber小于0的情況下等待,調(diào)用Wait();寫者在readerNumber大于0的情況下等待,調(diào)用Wait()。
在釋放資源時(shí)( finishReading()或finishWriting() ),如果readerNumber的值變?yōu)?,回到空閑狀態(tài),調(diào)用notifyAll(),通知潛在的等待者——讀者或?qū)懻摺?/p>
package thread;
import java.util.List;
import java.util.Iterator;
public class RunnableReader implements Runnable{
private RWResource resource = null;
public RunnableReader() {
}
/**
* must be called before start running
* @param theResource
*/
public void setRWResource(RWResource theResource){
resource = theResource;
}
public void run(){
while(true){
// get the reader's name
String readerName = "[" + Thread.currentThread().getName() + "] ";
// first, get buffer for reading
List buffer = resource.getBufferForReading();
// reading
for(Iterator iterator = buffer.iterator(); iterator.hasNext(); ){
System.out.println(readerName + iterator.next());
}
int articleNumber = buffer.size();
int thinkingTime = articleNumber * 1000;
for(int i = 0; i < thinkingTime; i++){
// thingking hard when reading
}
// finish reading
resource.finishReading();
// rest
try{
Thread.sleep(articleNumber * 50);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
上述代碼中的setRWResource()方法傳入共享資源——讀寫資源類。本例采用參數(shù)傳遞的方法,在讀者和寫者之間共享讀寫資源類。
Run()方法實(shí)現(xiàn)Runnable接口的Run()方法。首先,獲取當(dāng)前讀者(線程)的名稱,然后,試圖獲取讀資源——resource.getBufferForReading(),獲取資源之后,讀取buffer里面的所有文章,邊讀邊思考(注意代碼里面的for(int i = 0; i < thinkingTime; i++)行,占用cpu時(shí)間,表示思考過程),最后,釋放資源——resource.finishReading()。讀完文章,讀者休息一段時(shí)間——Thread.sleep(articleNumber * 50)。
注意,在以上的過程中,一定要嚴(yán)格遵守這樣的規(guī)定,在resource.getBufferForReading()和resource.finishReading()之間,進(jìn)行讀取操作。
package thread;
import java.util.List;
import java.util.Iterator;
public class RunnableWriter implements Runnable{
private RWResource resource = null;
private int articleNumber = 0;
public RunnableWriter() {
articleNumber = 0;
}
/**
* must be called before start running
* @param theResource
*/
public void setRWResource(RWResource theResource){
resource = theResource;
}
public void run(){
while(true){
// get the writer's name
String writerName = "[" + Thread.currentThread().getName() + "] ";
// first, get buffer for reading
List buffer = resource.getBufferForWriting();
int nWritten = 3; // write 4 articles one time
for(int n = 0; n< nWritten; n++){
// writing
articleNumber++;
String articleName = "article" + articleNumber;
buffer.add(articleName);
System.out.println(writerName + articleName);
int thinkingTime = 10000;
for (int i = 0; i < thinkingTime; i++) {
// thingking hard when writing
}
} // finish writing
resource.finishWriting();
// rest
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
上述代碼中的setRWResource()方法傳入共享資源——讀寫資源類。本例采用參數(shù)傳遞的方法,在讀者和寫者之間共享讀寫資源類。
Run()方法實(shí)現(xiàn)Runnable接口的Run()方法。首先,獲取當(dāng)前寫者(線程)的名稱,然后,試圖獲取寫資源——resource.getBufferForWriting(),獲取資源之后,開始向buffer里面寫3篇文章,邊寫邊思考(注意代碼里面的for(int i = 0; i < thinkingTime; i++)行,占用cpu時(shí)間,表示思考過程),最后,釋放資源——resource.finishWriting()。讀完文章,寫者休息一段時(shí)間——Thread.sleep(500)。
注意,在以上的過程中,一定要嚴(yán)格遵守這樣的規(guī)定,在resource.getBufferForWriting()和resource.finishWriting()之間,進(jìn)行寫操作。
package thread;
public class TestMain{
public static void main(String[] args) {
// init 生成共享資源
RWResource resource = new RWResource();
// 生成讀者,設(shè)置共享資源
RunnableReader reader = new RunnableReader();
reader.setRWResource(resource);
// 生成寫者,設(shè)置共享資源。
RunnableWriter writer = new RunnableWriter();
writer.setRWResource(resource);
int writerNumber = 5; // 5個(gè)寫者
int readerNumber = 10; // 10個(gè)讀者
// start writers 生成5個(gè)寫線程,并給每個(gè)線程起個(gè)名字,writer1, writer2…
for(int i = 0; i < writerNumber; i++){
Thread thread = new Thread(writer, "writer" + (i+1));
thread.start();
}
// give writers enough time to think and write articles
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
// start readers生成10個(gè)讀線程,并給每個(gè)線程起個(gè)名字,reader1, reader2…
for(int i = 0; i < readerNumber; i++){
Thread thread = new Thread(reader, "reader" + (i+1));
thread.start();
}
}
}
以上的測(cè)試類TestMain代碼,生成并運(yùn)行多個(gè)寫線程和讀線程,產(chǎn)生的結(jié)果可能如下:
[writer1] article1
[writer1] article2
[writer1] article3
…
[reader2] article1
[reader3] article1
[reader4] article1
[reader5] article1
[reader6] article1…
[reader1] article1
…
[writer3] article67
[writer3] article68
[writer3] article69
…
我們可以看到,Writer寫的文章的號(hào)碼從不相同,而且,每個(gè)Writer每次寫3篇文章,寫的過程從來不會(huì)被打斷。每個(gè)讀者每次通讀所有的文章,經(jīng)常有幾個(gè)讀者同時(shí)讀同一篇文章的情況。
這個(gè)例子采用“盒”包裝的方法,把“鎖”(readerNumber)和資源(buffer)放在同一個(gè)“盒子”(RWResource)里,但是,“盒子”對(duì)資源的包裝是不完全的,只是簡(jiǎn)單地把資源(buffer)返回給讀者和寫者,并沒有對(duì)資源(buffer)的訪問操作進(jìn)行封裝。
其實(shí),可以對(duì)資源(buffer)的訪問進(jìn)行進(jìn)一步封裝,比如,為“盒子”提供String[] readerBuffer()和writeBuffer(String)兩個(gè)方法,在這兩個(gè)方法里面,根據(jù)鎖(readerNumber)的狀態(tài),判斷讀寫操作是否合法,這樣,代碼的整體性會(huì)更好。帶來的結(jié)果是,RWResource類的調(diào)用規(guī)范和順序更加嚴(yán)格,必須在resource.getBufferForReading()和resource.finishReading()調(diào)用readerBuffer()方法,必須在resource.getBufferForWriting()和resource.finishWriting()之間調(diào)用writeBuffer()方法。否則,這兩個(gè)方法會(huì)報(bào)錯(cuò)。
這樣做會(huì)增加RWResource類的復(fù)雜度。還有一些設(shè)計(jì)上的因素需要考慮——readerBuffer和writeBuffer方法是否應(yīng)該被synchronized修飾?
從上例看到,在resource.getBufferForReading()和resource.finishReading()之間,進(jìn)行讀操作;在resource.getBufferForWriting()和resource.finishWriting()之間,進(jìn)行寫操作。讀操作和寫操作部分的代碼,是不用同步的。所以,在getBufferForReading()和finishReading()這樣的成對(duì)操作之間,用synchronized修飾readerBuffer和writeBuffer方法,是多此一舉。
但是,從代碼的完整性角度來看,因?yàn)閞eaderBuffer和writeBuffer方法需要讀 “鎖”的狀態(tài),所以,readerBuffer和writeBuffer方法還是加上synchronized修飾符為好。
考慮到這些因素,本例采取了一種折衷的方法。從形式上看,“鎖”和“資源”是聚合在一起的,實(shí)際上,兩者的操作是分開的,并不相關(guān)?!昂凶印备鶕?jù)“鎖”的狀態(tài),調(diào)整資源的分配,讀者和寫者得到資源之后,享有對(duì)資源的完全訪問權(quán)限。
另一個(gè)方向是把“資源”和“鎖”完全分開。把getBufferForReading方法改成startReading,把getBufferForWriting方法改成startWriting,RWResource不再分配資源,只進(jìn)行“鎖”操作。把RWResource該作RWLock類。
RunnableReader和RunnableWriter類各自增加一個(gè)setBuffer()方法,共享buffer資源。這樣,RunnableReader和RunnableWriter類就有了兩個(gè)分開的方法:setBuffer()設(shè)置共享資源,setRWLock()設(shè)置讀寫鎖。
對(duì)本例稍加修改,就可以實(shí)現(xiàn)上述的兩種思路。限于篇幅,這里不能給出完整的修改代碼。
本例中對(duì)讀寫資源類RWResource強(qiáng)加了調(diào)用順序。
在resource.getBufferForReading()和resource.finishReading()之間,進(jìn)行讀操作。
在resource.getBufferForWriting()和resource.finishWriting()之間,進(jìn)行寫操作。
要求在執(zhí)行一些處理之前,一定要執(zhí)行某項(xiàng)特殊操作,處理之后一定也要執(zhí)行某項(xiàng)特殊操作。這種人為的順序性,無疑增加了代碼的耦合度,降低了代碼的獨(dú)立性。很有可能會(huì)成為線程死鎖和資源操作沖突的根源。
這點(diǎn)一直讓我不安,可是沒有找到方法避免。畢竟,死鎖或者資源操作沖突,是線程的固有問題。
很巧的是,正在我惴惴不安的時(shí)候,我的一個(gè)朋友提供了一個(gè)信息。Sun公司根據(jù)JCR,決定在jdk1.5中引入關(guān)于concurrency(并發(fā))的部分。
以下這個(gè)網(wǎng)址是concurrency部分的util.concurrent一個(gè)實(shí)現(xiàn)。非常好的信息。對(duì)于處理多線程并發(fā)問題,很有幫助。
http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html
里面提供了一個(gè)ReadWriteLock類,標(biāo)準(zhǔn)用法如下。
Standard usage of ReadWriteLock:
class X {
ReadWriteLock rw;
// ...
public void read() throws InterruptedException {
rw.readLock().acquire();
try {
// ... do the read
}
finally {
rw.readlock().release()
}
}
public void write() throws InterruptedException {
rw.writeLock().acquire();
try {
// ... do the write
}
finally {
rw.writelock().release()
}
}
}
看完上述內(nèi)容,你們掌握J(rèn)ava 中怎么利用Thread實(shí)現(xiàn)讀寫同步的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。