我正在用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點
這不是完整的代碼,我認爲你選擇了代碼的錯誤部分來顯示。你的同步似乎沒有意義,因爲每個'Runnable'本身都是同步的,但我認爲問題不在那裏。請創建一個[mcve],刪除所有不必要的代碼,以使我們能夠重現問題。 – RealSkeptic
編輯它,現在它是一個最小,完整和功能的例子。 這只是添加,開始,下載和停止下載的可能性。 – Ronon
這遠非如此。最小意味着只保留需要演示問題的代碼部分。爲什麼需要添加鏈接,停止下載並更改數字?它不應該是*靈活*。無論用戶輸入什麼,它都應該運行一個簡單的問題演示。然後解釋程序在啓動時的功能,以及它應該做什麼。 – RealSkeptic