2015-12-25 70 views
1

我正在用JavaFx編程Java中的多文件下載程序,但我遇到了線程問題。
我遇到的問題是線程部分。
我想同時啓動多個下載(不同的URL /文件),例如兩個。如果我開始這兩個downloadthreads(我認爲)競賽情況發生,因爲兩個線程的文件名和文件大小是相同的,HDD上也只有一個文件,而不是預期的兩個。
我確定這是一個競態條件問題,但我該如何解決它?Java同步覆蓋值

Main.java

package de.minimal.program; 

import java.util.ArrayList; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.ThreadFactory; 

import de.minimal.program.model.Download; 
import de.minimal.program.util.Dl; 
import javafx.application.Application; 
import javafx.collections.FXCollections; 
import javafx.collections.ObservableList; 
import javafx.stage.Stage; 


public class Main extends Application { 

private ObservableList<Download> downloadData = FXCollections.observableArrayList(); 

private int i = 0; 

public ObservableList<Download> getDownloadData(){ 
    return downloadData; 
} 


@Override 
public void start(Stage primaryStage) { 
    downloadData.add(new Download("http://mirror.de.leaseweb.net/videolan/vlc/2.2.1/win32/vlc-2.2.1-win32.exe")); 
    downloadData.add(new Download("http://releases.ubuntu.com/15.10/ubuntu-15.10-desktop-amd64.iso")); 

    ArrayList<Thread> t = new ArrayList<Thread>(); 
    ExecutorService executor = Executors.newFixedThreadPool(2, new ThreadFactory() { 
      @Override 
      public Thread newThread(Runnable r) { 
       Thread a = new Thread(r); 
       a.setName("Thread " + i); 
       i++; 
       t.add(a); 
       return a; 
      } 
     }); 

    for(Download dl : downloadData){ 
     Dl d = new Dl(dl); 
     executor.execute(d); 
    } 
} 

public static void main(String[] args) { 
    launch(args); 
} 
} 

DL.java

package de.minimal.program.util; 

import java.util.List; 

import de.minimal.program.httpconnection.HttpConnection; 
import de.minimal.program.model.Download; 
import javafx.concurrent.Task; 

public class Dl extends Task<List<Download>> implements Runnable{ 

private Download download; 
private HttpConnection connection; 

public Dl(Download download){ 
    this.download = download; 

} 

@Override 
protected synchronized List<Download> call() throws Exception { 
    connection = new HttpConnection(download); 
    connection.downloadFile(); 
    return null; 
} 
} 

HTTPConnection.java

package de.minimal.program.httpconnection; 

import java.io.File; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.io.InputStream; 
import java.net.HttpURLConnection; 
import java.net.URL; 

import de.minimal.program.model.Download; 

    public class HttpConnection { 

    private static String url; 
    private Download download; 

    private static final int BUFFER_SIZE = 4096; 

    public HttpConnection(Download download){ 
    this.download = download; 
    } 

    public void downloadFile() throws IOException{ 

    String saveDir = download.getDownloadSavePath(); 

    url = download.getDownloadUrl(); 
    URL obj = new URL(url); 
    HttpURLConnection connection = (HttpURLConnection) obj.openConnection(); 
    connection.setRequestProperty("User-Agent", "Mozilla/5.0"); 
    // Forbid redirects for file resuming reasons 
    connection.setInstanceFollowRedirects(false); 

    int responseCode = connection.getResponseCode(); 

    // always check HTTP response code first 
    if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_PARTIAL) { 
     String fileName = ""; 
     String disposition = connection.getHeaderField("Content-Disposition"); 
     long contentLength = connection.getContentLengthLong(); 

     boolean appendToFile = false; 
     if(responseCode == HttpURLConnection.HTTP_PARTIAL) 
      appendToFile = true; 

     if(download.getFilesize() == 0){ 
      download.setFilesize(contentLength); 
     } 

     if (disposition != null) { 
      // extracts file name from header field 
      int index = disposition.indexOf("filename="); 
      if (index > 0) { 
       fileName = disposition.substring(index + 10, 
       disposition.length() - 1); 
      } 
     } else { 
      // extracts file name from URL 
      fileName = url.substring(url.lastIndexOf("/") + 1, url.length()); 
     } 

     download.setFilename(fileName); 

     // opens input stream from the HTTP connection 
     InputStream inputStream = connection.getInputStream(); 
     String saveFilePath = saveDir + File.separator + fileName; 

     // opens an output stream to save into file 
     FileOutputStream outputStream = new FileOutputStream(saveFilePath, appendToFile); 

     int bytesRead = -1; 
     long downloadedBytes = download.getTransferedBytes(); 
     long start = System.currentTimeMillis(); 
     byte[] buffer = new byte[BUFFER_SIZE]; 
     while ((bytesRead = inputStream.read(buffer)) != -1) { 
      outputStream.write(buffer, 0, bytesRead); 
      downloadedBytes += bytesRead; 

      if(System.currentTimeMillis() - start >= 2000){ 

       download.setTransferedBytes(downloadedBytes); 
       start = System.currentTimeMillis(); 
      } 
     } 

     outputStream.close(); 
     inputStream.close(); 

     System.out.println("Thread " + Thread.currentThread().getName() + " Filedownload " + fileName + " finished"); 

    } else { 
     System.out.println("No file to download. Server replied HTTP code: " + responseCode); 
    } 
    connection.disconnect(); 
} 
} 

Download.java

package de.minimal.program.model; 

import javafx.beans.property.LongProperty; 
import javafx.beans.property.SimpleLongProperty; 
import javafx.beans.property.SimpleStringProperty; 
import javafx.beans.property.StringProperty; 

public class Download { 

private final StringProperty filename; 
private final StringProperty filepath; 
private final LongProperty filesize; 
private final LongProperty transferedBytes; 
private String downloadUrl; 
private String downloadSavePath = "SET PATH "; 

public Download(){ 
    this(""); 
} 

public Download(String downloadUrl){ 
    this.downloadUrl = downloadUrl; 
    this.filename = new SimpleStringProperty(downloadUrl); 
    this.filepath = new SimpleStringProperty(downloadSavePath); 
    this.filesize = new SimpleLongProperty(0); 
    this.transferedBytes = new SimpleLongProperty(0); 
} 

// Filename 
public synchronized String getFilename(){ 
    return filename.get(); 
} 

public synchronized void setFilename(String filename){ 
    System.out.println("Thread " + Thread.currentThread().getName() + " Set filename: " + filename); 
    this.filename.set(filename); 
} 

public synchronized StringProperty filenameProperty(){ 
    return filename; 
} 

// Filepath 
public String getFilepath(){ 
    return filepath.get(); 
} 

public void setFilepath(String filepath){ 
    System.out.println("Set filepath: " + filepath); 
    this.filepath.set(filepath); 
} 

public StringProperty filepathProperty(){ 
    return filepath; 
} 

// Filesize 
public Long getFilesize(){ 
    return filesize.get(); 
} 

public void setFilesize(Long filesize){ 
    System.out.println("Thread " + Thread.currentThread().getName() + " Set filesize: " + filesize); 
    this.filesize.set(filesize); 
} 

public LongProperty filesizeProperty(){ 
    return filesize; 
} 

// TransferedBytes 
public Long getTransferedBytes(){ 
    return transferedBytes.get(); 
} 

public void setTransferedBytes(Long transferedBytes){ 
    System.out.println("Thread " + Thread.currentThread().getName() + " bytes transfered " + transferedBytes); 
    this.transferedBytes.set(transferedBytes); 
} 

public LongProperty transferedBytesProperty(){ 
    return transferedBytes; 
} 

// URL 
public String getDownloadUrl(){ 
    return downloadUrl; 
} 

public void setDownloadUrl(String downloadUrl){ 
    this.downloadUrl = downloadUrl; 
} 

// SavePath 
public String getDownloadSavePath(){ 
    return downloadSavePath; 
} 

public void setDownloadSavePath(String downloadSavePath){ 
    this.downloadSavePath = downloadSavePath; 
} 
} 

編輯:
這是最小的代碼。
您可以添加鏈接,啓動和停止下載,並更改同時發生的下載數量。

編輯2:再次 精縮了。希望這一次更好。 添加兩個下載鏈接並立即啓動它們。重現所提到的問題。編輯3: 解決了它。
問題是 私有靜態String url; 我記得我的教授曾經告訴過,靜態變量不是線程安全的。所以更多的信息可以在這裏找到 10 points about Static in Java第2點

+0

這不是完整的代碼,我認爲你選擇了代碼的錯誤部分來顯示。你的同步似乎沒有意義,因爲每個'Runnable'本身都是同步的,但我認爲問題不在那裏。請創建一個[mcve],刪除所有不必要的代碼,以使我們能夠重現問題。 – RealSkeptic

+0

編輯它,現在它是一個最小,完整和功能的例子。 這只是添加,開始,下載和停止下載的可能性。 – Ronon

+1

這遠非如此。最小意味着只保留需要演示問題的代碼部分。爲什麼需要添加鏈接,停止下載並更改數字?它不應該是*靈活*。無論用戶輸入什麼,它都應該運行一個簡單的問題演示。然後解釋程序在啓動時的功能,以及它應該做什麼。 – RealSkeptic

回答

1

在單個文件中寫入一個真正的約束?你可以做的是寫入單獨的文件,然後一旦文件完成,合併成一個單一的文件。

另外,如果從請求的結果並不那麼大,可以存放在內存,你可以直接從下載線程返回的文件,然後將其寫入到一個文件中。

+0

它們是不同的文件,例如一個下載是一個ubuntu文件,另一個是debian文件。所以不能合併任何東西 – Ronon

+0

我想我錯過了一些東西。你不需要同時下載多個文件並將它寫入單個文件嗎?我只是說,不是每個線程都寫在這個獨特的文件中,而是每個線程都可以寫入一個單獨的文件,然後將這兩個文件的內容合併成一個文件。這將解決您的併發問題。 –

+0

沒有每個線程正在處理它自己的文件。線程1正在從服務器a下載文件,線程2正在從服務器b下載文件,從服務器c下載線程3等等。 – Ronon