最近在开发一个跨平台通用下载器,然而在进行多线程测试时遇到了一个莫名其妙的问题,因为是多线程测试,最初采用exe文件进行测试,文件下载完成后大小异常且无法使用,由于exe文件只要任何一个字节的错误都会导致文件错误,所以无法定位错误到底有多大以及错误出现具体线程,而图片本身丢失一部分数据依旧可以显示,丢失的数据对应的部分会显示异常,所以为了进一步确定问题范围,改用图片(百度上随便找的)。
原图如下图,图片地址
下载代码如下:
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class downLoad {
//定义文件路径
static String path = new String("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1577292096535&di=f9dd01657df0d177384a9ab6fb221afb&imgtype=0&src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F711%2F0ZG3120214%2F130ZG20214-13.jpg");
//暂定为分三个线程下载数据
private static final int threadCounter = 3;
private static File dst;
public static void main(String[] args) {
int startIndex,endIndex;
//1.先拿到文件大小,用来计算每个线程下载的开始和结束位置
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
int code = conn.getResponseCode();
if(code == 200) {
//范围值为文件大小,单位是字节
int length = conn.getContentLength();
System.out.println(length);
//2.创建一个同待下载文件同样大小的文件,这里考虑到要进行随机存储
dst = new File("test.jpg");
//计算每个线程需要下载的字节数,设置开始和结束位置
int blockSize = length / threadCounter;
for(int i = 0;i<threadCounter;i++) {
startIndex = i * blockSize;
endIndex = (i+1) * blockSize-1;
if (i == threadCounter-1) {
endIndex = length -1;
}
//开启线程进行下载
DownLoadThread thread = new DownLoadThread(startIndex,endIndex);
thread.setName("线程" + (i + 1));
thread.start();
}
}
}catch (Exception e) {
// TODO: handle exception
}
}
private static class DownLoadThread extends Thread{
private int startIndex;
private int endIndex;
//通过构造方法传入下载开始和结束位置
public DownLoadThread(int startIndex,int endIndex) {
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void run() {
super.run();
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
//设置一个请求头Range,作用是高速服务器下载的开始和结束位置
conn.setRequestProperty("range", "bytes="+startIndex+"-"+endIndex);
int code = conn.getResponseCode();
//状态码206表示请求部分资源成功
if(code == 206) {
RandomAccessFile randomAccessFile = new RandomAccessFile(dst, "rw");
randomAccessFile.seek(startIndex);
InputStream inputStream = conn.getInputStream();
//将流写入到文件中
int len = -1;
byte[]buffer = new byte[1024];
while((len=inputStream.read(buffer))!=-1) {
randomAccessFile.write(buffer,0,len);
}
randomAccessFile.close();
}
System.out.println(Thread.currentThread().getName()+"下载完毕");
}catch (Exception e) {
// TODO: handle exception
}
}
}
}
这里我采用了三个线程下载这个图片,每个线程都下载1/3(最后一个线程略多几个字节,但不影响),但是运行后发现第一次下载下来的图片长这样:
通过这个图片初步可以推断应该是某一个或者某两个线程出现问题了,于是让线程打印线程id,进一步确定出错的线程,经过多次测试,发现三个线程中每次只能启动一个线程(且启动的线程完全随机),线程的启动顺序是JVM随机分配的,进一步测试发现,如果多次启动后能保证每个线程至少启动一次,则图片显示会和原图一致,这让我更加确定每个线程下载的数据本身都没有问题,问题出在每次都有两个线程无法启动。
既然确定了问题,接下来开始解决。。。。
猜测原因1:图片服务器本身的限制,虽然这个极不可能,毕竟线程数只有3,但本着严谨的原则还是进行的测试,测试方法为换一个图片服务器;
未完待续。。。。