基於Java多線程的下載器源碼剖析(二)

來源:互聯網
上載者:User
三:多個檔案下載的管理

這一節我們主要來講一下如何對多個檔案的下載進行管理

首先來看一下整個系統的UML圖



從最下面開始說起:

Download代表一個下載類,對每一個檔案都需要建立一個Download執行個體,用於對該檔案下載線程的管理。其中每個Download中都有以下幾個對象:

private ConcurrentLinkedQueue<DownloadBlock> blockQueue;private ConcurrentLinkedQueue<DownloaBlock> blockCache;private ConcurrentHashMap<Long, Long> blockCounts;private ConcurrentLinkedQueue<DownloadThread> activeThreads; 


其中

  1. blockQueue是一個隊列,用於儲存當前需要下載的DownloadBlock。Download對檔案進行切分形成的DownloadBlock會被放入到放入到blockQueue中,供以後的下載。
  2. blockCache為block記憶體緩衝池,主要是為了能夠複用已經建立好的DownloadBlock。
  3. blockCounts為一個Map,其中key為每個block的start值,而value為該block已經下載完的段D。主要作用是統計出當前已經每個Block已經下載完的段D,以計算即時下載速度
  4. activeThreads 主要是為了儲存該Thread中所有的活躍線程。


DownloadBlock是一個下載塊,其裡面有3個成員變數

private Download download; //其所屬的Downloadprivate long start; //下載檔案起始處private long length; //下載檔案的長度


DownloadThread是指下載進程,每個DownloadBlock都需要啟動一個DownloadThread去進行下載。即

new DownloadThread(block).start()


DownloadDeamon為了一個守護線程。其內部主要為了下載所有的需要下載DownloadBlock

private DownloadList downloads; //當前系統中所有的下載列表private ExecutorService threadPool; //線程池


Downloader 代表整個下載系統,整個系統中只有一個執行個體對象,因此我們需要保證系統中只有一個執行個體對象。

private DownloaderConfig config; // Downloader配置private DownloadList downloads; //當前系統所有的下載列表private Thread deamon; //守護進程private ConcurrentLinkedQueue<DownloadBlock> blockCache; //當前系統的緩衝private Timer timer; //


 

看了上面一大堆的東西,我保證你現在很暈,OK,我們從使用的角度來看整個系統是如何啟動並執行。

下面是範例程式碼。

public static void main(String[] args) {Downloader downloader = Downloader.getInstance();//下載第一個檔案String url1 = "https://tmsvm.googlecode.com/files/tmsvm_src_v1.1.0.rar";String saveFile1 = "data/tmsvm_src_v1.1.0.rar";DownloadConfig config =  new DownloadConfig();try {config.setUrl(new URL(url1));config.setFile(new File(saveFile1));config.setNthread(new Integer(5));config.setPriority(new Integer(6));//將第一個下載加入到下載列表中downloader.addDownload(new Download(config, downloader.getTimer()));} catch (MalformedURLException e) {// TODO Auto-generated catch blocke.printStackTrace();}//下載第二個檔案String url2 = "https://tmsvm.googlecode.com/files/Tmsvm%E5%8F%82%E8%80%83%E6%96%87%E6%A1%A3%28v1.1.0%29.rar";String saveFile2 = "data/Tmsvm參考文檔(v1.1.0).rar";try {config.setUrl(new URL(url2));config.setFile(new File(saveFile2));config.setNthread(new Integer(5));config.setPriority(new Integer(6));//將第二個下載加入到下載列表中downloader.addDownload(new Download(config, downloader.getTimer()));} catch (MalformedURLException e) {// TODO Auto-generated catch blocke.printStackTrace();}


1. 系統初始化


首先來看這一行行:

Downloader downloader = Downloader.getInstance();

Downloader是這個下載器的總調度師,一山不容二虎,當然在系統運行過程中,只能有一個Downloader的執行個體,因此我們需要用單例模式來保證這一點。

首先要取得downloader執行個體,即系統的初始化。我們看系統初始化需要做什嗎?

public static Downloader getInstance(){if(downloader == null)downloader = new Downloader(new DownloaderConfig());return downloader;}private Downloader(DownloaderConfig config) {super();this.config = config;start();}

上面的代碼中的start()函數中到底做了什麼呢?

  1. 初始化blockCache緩衝,其中blockCache為ConcurrentLinkedQueue<DownloadBlock>類型。
  2. 啟動守護進程DownloadDeamon 

具體代碼如下:

private void start(){blockCache = new ConcurrentLinkedQueue<DownloadBlock>(); //初始化緩衝downloads = new DownloadList();deamon = new Thread(new DownloadDeamon(downloads)); //初始化守護進程deamon.setDaemon(true); deamon.start();timer = new Timer(Constants.TIMER_NAME, true);}


上面代碼中啟動了一個守護進程。那麼這個守護進程在啟動的時候在做什麼事情呢?
我們來看一下他的run()函數

public void run() {System.out.println("Create thread pool");threadPool = Executors.newCachedThreadPool(); //初始化線程池DownloadBlock block;while(true){block = getDownloadBlock(); //不斷從當前系統中擷取待下載的DownloadBlockif(block != null){log.info("Create new download thread for " + block);//啟動線程執行下載threadPool.execute(new DownloadThread(block)); //將當前Block從其所在的Download中移除block.getDownload().removeDownloadBlock(block);}try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blockSystem.out.println("Download deamon stoped by user");break;}}}


守護進程所做的事情就是不斷擷取將要進行下載的Block,然後啟動線程去進行下載。
來看一下擷取Block的策略:這裡不斷的從當前下載列表中擷取所有的Download,然後從裡面選取最需要下載的檔案,“最需要下載”定義為剩餘的待下載量最多。其具體的代碼看下方:

private DownloadBlock getDownloadBlock(){downs= downloads.toArray();if(downs== null || downs.length == 0)return null;Downloaddownload = downs[0];for(Downloaddown : downs){//找最需要下載的Download進行下載 if(down.getRemainThread()> download.getRemainThread())download= down;}download.descRemainThread();returndownload.getDownloadBlock();}


2. 建立下載

上面講解的是系統初始化所做的事情。那麼當我們把開始下載一個檔案時系統是怎麼啟動並執行呢?


//下載第一個檔案String url1 = "https://tmsvm.googlecode.com/files/tmsvm_src_v1.1.0.rar";String saveFile1 = "data/tmsvm_src_v1.1.0.rar";DownloadConfig config =  new DownloadConfig();try {config.setUrl(new URL(url1));config.setFile(new File(saveFile1));config.setNthread(new Integer(5));config.setPriority(new Integer(6));//將第一個下載加入到下載列表中downloader.addDownload(new Download(config, downloader.getTimer()));} catch (MalformedURLException e) {// TODO Auto-generated catch blocke.printStackTrace();}

我們重點來看這一句:

//將第一個下載加入到下載列表中downloader.addDownload(new Download(config, downloader.getTimer()));

addDownload的定義如下

public boolean addDownload(Download download){new Thread(download).start();return downloads.add(download);}

這段代碼做了兩件事情:

  1. 為Download啟動一下線程。Download線程所做的事情就是把當前的檔案根據線程數目進行切分。
  2.  把當前Download加入到DownloadList中。

OK,我們看看,Download是切分檔案時是如何與整個系統聯絡在一起。

public void run(){try {begin = System.currentTimeMillis();// get lengthlog.info("Begin download " + config.getUrl());length = config.getUrl().openConnection().getContentLength();log.info("Total size : " + length);// create filelog.info("Create file " + config.getFile());RandomAccessFile file = new RandomAccessFile(config.getFile(), "rw");file.setLength(length);log.info("Created with length = " + length);file.close();int size = length / config.getNthread();// add initial blockslog.debug("Add initial " + config.getNthread() + " download blocks");for(int i = 0; i < config.getNthread(); i++){int start = i * size;int len;if(i == config.getNthread() - 1)len = length - start;else len = size;addDownloadBlock(getDownloadBlock(start, len));}// set task that checks speed every 1 secondlog.debug("Set task for speed check");checkSpeedTask = new CheckSpeedTask(this, System.currentTimeMillis()-10, blockCounts);//timer.schedule(checkSpeedTask, 1000, 1000);// set task that creates new blocks every 1 minutelog.debug("Set task for split blocks");splitBlockTask = new SplitBlockTask(this, System.currentTimeMillis()-10, blockCounts, activeThreads);timer.schedule(splitBlockTask, 60*1000, 60*1000);// wait for all blocks completelog.debug("Waiting for all blocks to complete");while(activeThreads.size() > 0 || blockQueue.size() > 0){Thread.sleep(1000);checkSpeed();}// stop the taskscheckSpeedTask.cancel();splitBlockTask.cancel();long total = System.currentTimeMillis() - begin;speed = length/(total/1000);log.info("Complete download " + config.getUrl() + "\n"+ "Total time : " + total + " ms" + "\n"+ "Average speed: " + speed + "Byte/s");log.debug(this + " put all block in blockCache back to downloader system");for(DownloadBlock block : blockCache){Downloader.getInstance().putDownloadBlock(block);}} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}

上面的代碼,我們在第一篇中已經講解過了,這裡我們會重點看

addDownloadBlock(getDownloadBlock(start, len));

其意思是將當前的切分出來的Block放入到待下載隊列中去。

而我們在守護進程那裡,看到他不斷的會從當前系統中找最需要下載的Download,然後再從Download中取出下載隊列的Block進行下載

//DownloadDemen不斷的擷取DownloadBlockwhile(true){block = getDownloadBlock();if(block != null){System.out.println("Create new download thread for " + block);threadPool.execute(new DownloadThread(block));block.getDownload().removeDownloadBlock(block);}}

//getDownloadBlock()定義如下:private DownloadBlock getDownloadBlock(){   downs= downloads.toArray();   if(downs== null || downs.length == 0)     return null;   Downloaddownload = downs[0];   for(Downloaddown : downs){     if(down.getRemainThread()> download.getRemainThread())              download= down;   }   download.descRemainThread();   return download.getDownloadBlock();}

//而download.getDownloadBlock()定義如下所示:public DownloadBlock getDownloadBlock(){return blockQueue.peek();}

寫到這裡,整個的系統架構目錄就非常清晰了:Downloader, DownloadDemen, Download 之間是通過DownloadBlock聯絡起來的。
當有一個檔案需要下載時,Downloader 把該Download加入到DownloadList中。而Download自身會通過切分檔案建立出多個DownloadBlock。DownloadDemen每時每刻都在擷取DownloadBlock,賦予其線程進行下載。

下一節,我們會重點講解一下Downloader如何系統中的緩衝進行處理的。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.