2012-04-02 132 views
13

我有一個java程序,它使用20個線程。他們每個人都將他們的結果寫入一個名爲output.txt的文件中。主題和文件寫作

我總是會在output.txt中獲得不同的行數。

它可能是線程同步的問題嗎?有沒有辦法解決這個問題?

+0

嗯,這不是很清楚你的實現如何。正如我簡單的測試案例所顯示的那樣,使用20個線程的FileWriter可以獲得恆定的輸出行。可能需要添加一些實現細節。看到我的答案。 – FaithReaper 2018-01-24 09:01:37

回答

26

它可以是線程同步的問題嗎?

是的。

有辦法解決這個問題嗎?

是的,確保通過在相關互斥鎖上進行同步來進行寫操作。或者,只有一個線程實際輸出到文件,並讓所有其他線程簡單地將文本排隊寫入到一個寫入線程從中抽取的隊列中。 (這樣的20個主要線程不會在I/O塊。)

重新互斥:舉例來說,如果他們都使用相同的FileWriter實例(或其他),我將把爲fw,那麼他們可以使用它作爲一個互斥體:

synchronized (fw) { 
    fw.write(...); 
} 

如果他們各自使用自己的FileWriter或什麼的,找點別的事情,他們都共享是互斥。

但是,讓線程代表其他人進行I/O操作也可能是一個好方法。

+0

您能否更具體地說明一種編寫器線程方法的最佳方法?例如。也許使用[單線程執行程序](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor--),然後將任務提交給該執行程序'? – Roland 2017-05-22 07:40:22

9

我建議你以這種方式組織它:一個線程使用者將消耗所有數據並將其寫入文件。所有工作線程將以同步方式向消費者線程生成數據。或者使用多線程文件寫入,您可以使用一些互斥鎖或鎖定實現。

+1

+1我剛剛在寫你的時候給我的答案加了這個建議。 :-) – 2012-04-02 07:47:07

1

在這種情況下,您應該使用同步。想象一下,2個線程(t1和t2)同時打開文件並開始寫入。第一個線程執行的更改由第二個線程覆蓋,因爲第二個線程是最後一個將更改保存到文件。當線程t1正在寫入文件時,t2必須等到t1完成它的任務之後才能打開它。

2

如果您想要任何性能和易管理性的外表,請按照Alex和其他人的建議,與生產者 - 消費者隊列和僅僅一個文件編寫器一起使用。使用互斥鎖讓文件中的所有線程都很麻煩 - 每個磁盤延遲都直接傳送到主應用程序功能中(增加了爭用)。這對於緩慢的網絡驅動器來說尤其沒有,因爲這些網絡驅動器會在沒有警告的情況下消失。

1

如果你可以拿着你的文件作爲FileOutputStream可以將其鎖定這樣的:

FileOutputStream file = ... 
.... 
// Thread safe version. 
void write(byte[] bytes) { 
    try { 
    boolean written = false; 
    do { 
     try { 
     // Lock it! 
     FileLock lock = file.getChannel().lock(); 
     try { 
      // Write the bytes. 
      file.write(bytes); 
      written = true; 
     } finally { 
      // Release the lock. 
      lock.release(); 
     } 
     } catch (OverlappingFileLockException ofle) { 
     try { 
      // Wait a bit 
      Thread.sleep(0); 
     } catch (InterruptedException ex) { 
      throw new InterruptedIOException ("Interrupted waiting for a file lock."); 
     } 
     } 
    } while (!written); 
    } catch (IOException ex) { 
    log.warn("Failed to lock " + fileName, ex); 
    } 
} 
+0

鑑於'synchronized'存在,這完全是多餘的。 – EJP 2017-09-19 06:12:33

+0

@EJP - 請參閱[FileLock](https://docs.oracle.com/javase/7/docs/api/java/nio/channels/FileLock.html) - *文件上的鎖應該對所有人都可見無論程序編寫的語言如何,都可以訪問該文件* - 理論上講,它們應該比'同步'更好,但通常不會。 – OldCurmudgeon 2017-09-19 08:20:43

+0

這不會使它不是多餘的,或者比'同步'更好。問題中沒有其他過程或其他語言。 – EJP 2018-01-28 05:49:58

0

好,沒有任何實現細節,這是很難知道的,但我的測試情況表明,我總是得到220行輸出,即恆定行數,與FileWriter。請注意,此處不使用​​。

import java.io.File; 
import java.io.FileWriter; 
import java.io.IOException; 
/** 
* Working example of synchonous, competitive writing to the same file. 
* @author WesternGun 
* 
*/ 
public class ThreadCompete implements Runnable { 
    private FileWriter writer; 
    private int status; 
    private int counter; 
    private boolean stop; 
    private String name; 


    public ThreadCompete(String name) { 
     this.name = name; 
     status = 0; 
     stop = false; 
     // just open the file without appending, to clear content 
     try { 
      writer = new FileWriter(new File("test.txt"), true); 
     } catch (IOException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 

    } 


    public static void main(String[] args) { 

     for (int i=0; i<20; i++) { 
      new Thread(new ThreadCompete("Thread" + i)).start(); 
     } 
    } 

    private int generateRandom(int range) { 
     return (int) (Math.random() * range); 
    } 

    @Override 
    public void run() { 
     while (!stop) { 
      try { 
       writer = new FileWriter(new File("test.txt"), true); 
       if (status == 0) { 
        writer.write(this.name + ": Begin: " + counter); 
        writer.write(System.lineSeparator()); 
        status ++; 
       } else if (status == 1) { 
        writer.write(this.name + ": Now we have " + counter + " books!"); 
        writer.write(System.lineSeparator()); 
        counter++; 
        if (counter > 8) { 
         status = 2; 
        } 

       } else if (status == 2) { 
        writer.write(this.name + ": End. " + counter); 
        writer.write(System.lineSeparator()); 
        stop = true; 
       } 
       writer.flush(); 
       writer.close(); 
      } catch (IOException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
     } 
    } 
} 

據我瞭解(和測試),有兩個階段在這個過程中:

  • 所有線程池中的所有創建和啓動,準備搶的文件;
  • 其中之一抓住它,我想它然後在內部鎖定它,阻止其他線程獲得訪問,因爲我從來沒有看到一個行來自兩個線程的內容組合。所以,當一個線程正在寫入時,其他人正在等待,直到完成該線路,並且很可能會釋放該文件。 因此,不會出現競賽狀況。
  • 其他人最快抓取文件並開始寫作。

那麼,它就像一個人羣等待浴室外,無需排隊.....

所以,如果你的實現是不同的,顯示代碼,我們可以幫助打破它。