diff --git a/app/src/main/java/eu/kanade/mangafeed/data/helpers/DownloadManager.java b/app/src/main/java/eu/kanade/mangafeed/data/helpers/DownloadManager.java
index 3aa2ee4f5..63dc14949 100644
--- a/app/src/main/java/eu/kanade/mangafeed/data/helpers/DownloadManager.java
+++ b/app/src/main/java/eu/kanade/mangafeed/data/helpers/DownloadManager.java
@@ -2,11 +2,21 @@ package eu.kanade.mangafeed.data.helpers;
 
 import android.content.Context;
 
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+
 import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
 import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
 import java.util.List;
 
 import eu.kanade.mangafeed.data.models.Chapter;
+import eu.kanade.mangafeed.data.models.Download;
 import eu.kanade.mangafeed.data.models.Manga;
 import eu.kanade.mangafeed.data.models.Page;
 import eu.kanade.mangafeed.events.DownloadChapterEvent;
@@ -20,77 +30,107 @@ import rx.subjects.PublishSubject;
 public class DownloadManager {
 
     private PublishSubject<DownloadChapterEvent> downloadsSubject;
-    private Subscription downloadsSubscription;
+    private Subscription downloadSubscription;
 
     private Context context;
     private SourceManager sourceManager;
     private PreferencesHelper preferences;
+    private Gson gson;
+
+    private List<Download> queue;
 
     public DownloadManager(Context context, SourceManager sourceManager, PreferencesHelper preferences) {
         this.context = context;
         this.sourceManager = sourceManager;
         this.preferences = preferences;
+        this.gson = new Gson();
+
+        queue = new ArrayList<>();
 
         initializeDownloadSubscription();
     }
 
+    public PublishSubject<DownloadChapterEvent> getDownloadsSubject() {
+        return downloadsSubject;
+    }
+
     private void initializeDownloadSubscription() {
-        if (downloadsSubscription != null && !downloadsSubscription.isUnsubscribed()) {
-            downloadsSubscription.unsubscribe();
+        if (downloadSubscription != null && !downloadSubscription.isUnsubscribed()) {
+            downloadSubscription.unsubscribe();
         }
 
         downloadsSubject = PublishSubject.create();
 
-        downloadsSubscription = downloadsSubject
+        // Listen for download events, add them to queue and download
+        downloadSubscription = downloadsSubject
                 .subscribeOn(Schedulers.io())
-                .concatMap(event -> downloadChapter(event.getManga(), event.getChapter()))
+                .filter(event -> !isChapterDownloaded(event))
+                .flatMap(this::createDownload)
+                .window(preferences.getDownloadThreads())
+                .concatMap(concurrentDownloads -> concurrentDownloads
+                        .concatMap(this::downloadChapter))
                 .onBackpressureBuffer()
                 .subscribe();
     }
 
-    public Observable<Page> downloadChapter(Manga manga, Chapter chapter) {
-        final Source source = sourceManager.get(manga.source);
-        final File chapterDirectory = getAbsoluteChapterDirectory(source, manga, chapter);
+    // Check if a chapter is already downloaded
+    private boolean isChapterDownloaded(DownloadChapterEvent event) {
+        final Source source = sourceManager.get(event.getManga().source);
 
-        return source
-                .pullPageListFromNetwork(chapter.url)
-                // Ensure we don't download a chapter already downloaded
-                .filter(pages -> !isChapterDownloaded(chapterDirectory, pages))
+        // If the chapter is already queued, don't add it again
+        for (Download download : queue) {
+            if (download.chapter.id == event.getChapter().id)
+                return true;
+        }
+
+        // If the directory doesn't exist, the chapter isn't downloaded
+        File dir = getAbsoluteChapterDirectory(source, event.getManga(), event.getChapter());
+        if (!dir.exists())
+            return false;
+
+        // If the page list doesn't exist, the chapter isn't download (or maybe it's,
+        // but we consider it's not)
+        List<Page> savedPages = getSavedPageList(source, event.getManga(), event.getChapter());
+        if (savedPages == null)
+            return false;
+
+        // If the number of files matches the number of pages, the chapter is downloaded.
+        // We have the index file, so we check one file less
+        return (dir.listFiles().length - 1) == savedPages.size();
+    }
+
+    // Create a download object and add it to the downloads queue
+    private Observable<Download> createDownload(DownloadChapterEvent event) {
+        Download download = new Download(
+                sourceManager.get(event.getManga().source),
+                event.getManga(),
+                event.getChapter());
+
+        download.directory = getAbsoluteChapterDirectory(
+                download.source, download.manga, download.chapter);
+
+        queue.add(download);
+        return Observable.just(download);
+    }
+
+    // Download the entire chapter
+    private Observable<Page> downloadChapter(Download download) {
+        return download.source
+                .pullPageListFromNetwork(download.chapter.url)
+                .subscribeOn(Schedulers.io())
+                // Add resulting pages to download object
+                .doOnNext(pages -> download.pages = pages)
                 // Get all the URLs to the source images, fetch pages if necessary
                 .flatMap(pageList -> Observable.merge(
                         Observable.from(pageList).filter(page -> page.getImageUrl() != null),
-                        source.getRemainingImageUrlsFromPageList(pageList)))
-                // Start downloading images
-                .flatMap(page -> getDownloadedImage(page, source, chapterDirectory));
-    }
-    
-    public File getAbsoluteChapterDirectory(Source source, Manga manga, Chapter chapter) {
-        return new File(preferences.getDownloadsDirectory(),
-                getChapterDirectory(source, manga, chapter));
-    }
-
-    public String getChapterDirectory(Source source, Manga manga, Chapter chapter) {
-        return source.getName() +
-                File.separator +
-                manga.title.replaceAll("[^a-zA-Z0-9.-]", "_") +
-                File.separator +
-                chapter.name.replaceAll("[^a-zA-Z0-9.-]", "_");
-    }
-
-    private String getImageFilename(Page page) {
-        return page.getImageUrl().substring(
-                page.getImageUrl().lastIndexOf("/") + 1,
-                page.getImageUrl().length());
-    }
-
-    private boolean isChapterDownloaded(File chapterDir, List<Page> pages) {
-        return chapterDir.exists() && chapterDir.listFiles().length == pages.size();
-    }
-
-    private boolean isImageDownloaded(File imagePath) {
-        return imagePath.exists() && !imagePath.isDirectory();
+                        download.source.getRemainingImageUrlsFromPageList(pageList)))
+                // Start downloading images, consider we can have downloaded images already
+                .concatMap(page -> getDownloadedImage(page, download.source, download.directory))
+                // Remove from the queue
+                .doOnCompleted(() -> removeFromQueue(download));
     }
 
+    // Get downloaded image if exists, otherwise download it with the method below
     public Observable<Page> getDownloadedImage(final Page page, Source source, File chapterDir) {
         Observable<Page> obs = Observable.just(page);
         if (page.getImageUrl() == null)
@@ -114,6 +154,7 @@ public class DownloadManager {
         });
     }
 
+    // Download the image
     private Observable<Page> downloadImage(final Page page, Source source, File chapterDir, String imageFilename) {
         return source.getImageProgressResponse(page)
                 .flatMap(resp -> {
@@ -127,8 +168,62 @@ public class DownloadManager {
                 });
     }
 
-    public PublishSubject<DownloadChapterEvent> getDownloadsSubject() {
-        return downloadsSubject;
+    // Get the filename for an image given the page
+    private String getImageFilename(Page page) {
+        return page.getImageUrl().substring(
+                page.getImageUrl().lastIndexOf("/") + 1,
+                page.getImageUrl().length());
+    }
+
+    private boolean isImageDownloaded(File imagePath) {
+        return imagePath.exists() && !imagePath.isDirectory();
+    }
+
+    private void removeFromQueue(final Download download) {
+        savePageList(download.source, download.manga, download.chapter, download.pages);
+        queue.remove(download);
+    }
+
+    // Return the page list from the chapter's directory if it exists, null otherwise
+    public List<Page> getSavedPageList(Source source, Manga manga, Chapter chapter) {
+        File chapterDir = getAbsoluteChapterDirectory(source, manga, chapter);
+        File pagesFile = new File(chapterDir, "index.json");
+
+        try {
+            JsonReader reader = new JsonReader(new FileReader(pagesFile.getAbsolutePath()));
+
+            Type collectionType = new TypeToken<List<Page>>() {}.getType();
+            return gson.fromJson(reader, collectionType);
+        } catch (FileNotFoundException e) {
+            return null;
+        }
+    }
+
+    // Save the page list to the chapter's directory
+    public void savePageList(Source source, Manga manga, Chapter chapter, List<Page> pages) {
+        File chapterDir = getAbsoluteChapterDirectory(source, manga, chapter);
+        File pagesFile = new File(chapterDir, "index.json");
+
+        FileOutputStream out;
+        try {
+            out = new FileOutputStream(pagesFile);
+            out.write(gson.toJson(pages).getBytes());
+            out.flush();
+            out.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    // Get the absolute path to the chapter directory
+    public File getAbsoluteChapterDirectory(Source source, Manga manga, Chapter chapter) {
+        String chapterRelativePath = source.getName() +
+                File.separator +
+                manga.title.replaceAll("[^a-zA-Z0-9.-]", "_") +
+                File.separator +
+                chapter.name.replaceAll("[^a-zA-Z0-9.-]", "_");
+
+        return new File(preferences.getDownloadsDirectory(), chapterRelativePath);
     }
 
 }
diff --git a/app/src/main/java/eu/kanade/mangafeed/data/helpers/PreferencesHelper.java b/app/src/main/java/eu/kanade/mangafeed/data/helpers/PreferencesHelper.java
index 51b400eec..c2b3f7ca0 100644
--- a/app/src/main/java/eu/kanade/mangafeed/data/helpers/PreferencesHelper.java
+++ b/app/src/main/java/eu/kanade/mangafeed/data/helpers/PreferencesHelper.java
@@ -59,4 +59,8 @@ public class PreferencesHelper {
                 DiskUtils.getStorageDirectories(context)[0]);
     }
 
+    public int getDownloadThreads() {
+        return Integer.parseInt(mPref.getString(getKey(R.string.pref_download_threads_key), "1"));
+    }
+
 }
diff --git a/app/src/main/java/eu/kanade/mangafeed/data/models/Download.java b/app/src/main/java/eu/kanade/mangafeed/data/models/Download.java
new file mode 100644
index 000000000..f55d184dd
--- /dev/null
+++ b/app/src/main/java/eu/kanade/mangafeed/data/models/Download.java
@@ -0,0 +1,20 @@
+package eu.kanade.mangafeed.data.models;
+
+import java.io.File;
+import java.util.List;
+
+import eu.kanade.mangafeed.sources.base.Source;
+
+public class Download {
+    public Source source;
+    public Manga manga;
+    public Chapter chapter;
+    public List<Page> pages;
+    public File directory;
+
+    public Download(Source source, Manga manga, Chapter chapter) {
+        this.source = source;
+        this.manga = manga;
+        this.chapter = chapter;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/mangafeed/presenter/MangaChaptersPresenter.java b/app/src/main/java/eu/kanade/mangafeed/presenter/MangaChaptersPresenter.java
index fa41f545f..90c59577e 100644
--- a/app/src/main/java/eu/kanade/mangafeed/presenter/MangaChaptersPresenter.java
+++ b/app/src/main/java/eu/kanade/mangafeed/presenter/MangaChaptersPresenter.java
@@ -15,6 +15,7 @@ import eu.kanade.mangafeed.data.helpers.SourceManager;
 import eu.kanade.mangafeed.data.models.Chapter;
 import eu.kanade.mangafeed.data.models.Manga;
 import eu.kanade.mangafeed.events.ChapterCountEvent;
+import eu.kanade.mangafeed.events.DownloadChapterEvent;
 import eu.kanade.mangafeed.events.SourceMangaChapterEvent;
 import eu.kanade.mangafeed.sources.base.Source;
 import eu.kanade.mangafeed.ui.fragment.MangaChaptersFragment;
@@ -38,7 +39,8 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment>
     private static final int DB_CHAPTERS = 1;
     private static final int ONLINE_CHAPTERS = 2;
 
-    private Subscription menuOperationSubscription;
+    private Subscription markReadSubscription;
+    private Subscription downloadSubscription;
 
     @Override
     protected void onCreate(Bundle savedState) {
@@ -90,10 +92,6 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment>
         }
     }
 
-    public Manga getManga() {
-        return manga;
-    }
-
     public void refreshChapters() {
         if (getView() != null)
             getView().setSwipeRefreshing();
@@ -120,10 +118,10 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment>
     }
 
     public void markChaptersRead(Observable<Chapter> selectedChapters, boolean read) {
-        if (menuOperationSubscription != null)
-            remove(menuOperationSubscription);
+        if (markReadSubscription != null)
+            remove(markReadSubscription);
 
-        add(menuOperationSubscription = selectedChapters
+        add(markReadSubscription = selectedChapters
                 .subscribeOn(Schedulers.io())
                 .map(chapter -> {
                     chapter.read = read;
@@ -137,6 +135,18 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment>
                 }));
     }
 
+    public void downloadChapters(Observable<Chapter> selectedChapters) {
+        if (downloadSubscription != null)
+            remove(downloadSubscription);
+
+        add(downloadSubscription = selectedChapters
+                .subscribeOn(Schedulers.io())
+                .subscribe(chapter -> {
+                    EventBus.getDefault().post(
+                            new DownloadChapterEvent(manga, chapter));
+                }));
+    }
+
     public void checkIsChapterDownloaded(Chapter chapter) {
         File dir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter);
 
diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/fragment/MangaChaptersFragment.java b/app/src/main/java/eu/kanade/mangafeed/ui/fragment/MangaChaptersFragment.java
index 0fb6d30e4..cdaae91e6 100644
--- a/app/src/main/java/eu/kanade/mangafeed/ui/fragment/MangaChaptersFragment.java
+++ b/app/src/main/java/eu/kanade/mangafeed/ui/fragment/MangaChaptersFragment.java
@@ -18,11 +18,9 @@ import java.util.List;
 
 import butterknife.Bind;
 import butterknife.ButterKnife;
-import de.greenrobot.event.EventBus;
 import eu.kanade.mangafeed.R;
 import eu.kanade.mangafeed.data.models.Chapter;
 import eu.kanade.mangafeed.data.services.DownloadService;
-import eu.kanade.mangafeed.events.DownloadChapterEvent;
 import eu.kanade.mangafeed.presenter.MangaChaptersPresenter;
 import eu.kanade.mangafeed.ui.activity.MangaDetailActivity;
 import eu.kanade.mangafeed.ui.activity.ReaderActivity;
@@ -31,8 +29,6 @@ import eu.kanade.mangafeed.ui.adapter.ChaptersAdapter;
 import eu.kanade.mangafeed.ui.fragment.base.BaseRxFragment;
 import nucleus.factory.RequiresPresenter;
 import rx.Observable;
-import rx.Subscription;
-import rx.schedulers.Schedulers;
 
 @RequiresPresenter(MangaChaptersPresenter.class)
 public class MangaChaptersFragment extends BaseRxFragment<MangaChaptersPresenter> implements
@@ -44,7 +40,6 @@ public class MangaChaptersFragment extends BaseRxFragment<MangaChaptersPresenter
     private ChaptersAdapter adapter;
 
     private ActionMode actionMode;
-    private Subscription downloadSubscription;
 
     public static Fragment newInstance() {
         return new MangaChaptersFragment();
@@ -146,7 +141,7 @@ public class MangaChaptersFragment extends BaseRxFragment<MangaChaptersPresenter
                 getPresenter().markChaptersRead(getSelectedChapters(), false);
                 return true;
             case R.id.action_download:
-                onDownloadChapters();
+                getPresenter().downloadChapters(getSelectedChapters());
                 return true;
         }
         return false;
@@ -207,19 +202,4 @@ public class MangaChaptersFragment extends BaseRxFragment<MangaChaptersPresenter
         actionMode.setTitle(getString(R.string.selected_chapters_title, count));
     }
 
-    private void onDownloadChapters() {
-        if (downloadSubscription != null && !downloadSubscription.isUnsubscribed()) {
-            downloadSubscription.unsubscribe();
-            downloadSubscription = null;
-        }
-
-        downloadSubscription = getSelectedChapters()
-                .subscribeOn(Schedulers.io())
-                .subscribe(chapter -> {
-                    EventBus.getDefault().post(
-                            new DownloadChapterEvent(getPresenter().getManga(), chapter));
-                    downloadSubscription.unsubscribe();
-                });
-    }
-
 }
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index a89cd1024..81aa3dc97 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -14,4 +14,10 @@
         <item>4</item>
     </string-array>
 
+    <string-array name="download_threads">
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+    </string-array>
+
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml
index 3e7b28ee4..04ea8b2c2 100644
--- a/app/src/main/res/values/keys.xml
+++ b/app/src/main/res/values/keys.xml
@@ -6,4 +6,5 @@
     <string name="pref_fullscreen_key">pref_fullscreen_key</string>
     <string name="pref_default_viewer_key">pref_default_viewer_key</string>
     <string name="pref_download_directory_key">pref_download_directory_key</string>
+    <string name="pref_download_threads_key">pref_download_threads_key</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 265678495..a084b9b6f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -92,5 +92,6 @@
     <string name="notification_completed">Update completed</string>
     <string name="notification_no_new_chapters">No new chapters found</string>
     <string name="notification_new_chapters">Found new chapters for:</string>
+    <string name="pref_download_threads">Download threads</string>
 
 </resources>
diff --git a/app/src/main/res/xml/pref_downloads.xml b/app/src/main/res/xml/pref_downloads.xml
index 70056be23..7c79e587d 100644
--- a/app/src/main/res/xml/pref_downloads.xml
+++ b/app/src/main/res/xml/pref_downloads.xml
@@ -2,4 +2,11 @@
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
     android:orderingFromXml="true">
 
+    <ListPreference android:title="@string/pref_download_threads"
+        android:key="@string/pref_download_threads_key"
+        android:entries="@array/download_threads"
+        android:entryValues="@array/download_threads"
+        android:defaultValue="1"
+        android:summary="%s"/>
+
 </PreferenceScreen>
\ No newline at end of file