這不就是多線程ThreadPoolExecutor和阻塞隊列嗎

来源:https://www.cnblogs.com/dongguangming/archive/2020/04/16/12709972.html
-Advertisement-
Play Games

無處不在的線程,多線程,阻塞隊列,併發 編程世界無新鮮事,看你翻牆翻得厲不厲害 場景:現在的軟體開發迭代速度(一周一更新,甚至一天一發佈)真是太快了,今天進行軟體更新的時候,看到了有趣的現象,這不就是線程池,ThreadPoolExecutor,阻塞隊列,任務(下載和安裝)最好的案例嘛!經常看到很多 ...


無處不在的線程,多線程,阻塞隊列,併發

編程世界無新鮮事,看你翻牆翻得厲不厲害

場景:現在的軟體開發迭代速度(一周一更新,甚至一天一發佈)真是太快了,今天進行軟體更新的時候,看到了有趣的現象,這不就是線程池,ThreadPoolExecutor,阻塞隊列,任務(下載和安裝)最好的案例嘛!經常看到很多博文在寫多線程,併發,隊列,卻舉不出現實生活的場景例子,都在背書嗎(天下文章一大抄,看你會抄不會抄)。

現象圖示:



我開啟了全部更新38個要更新的app,可最多時看到了3個在同時下載,剩下的下載任務在排隊(隊列),安裝過程中,明明已經下載了多個app,可同一時刻只有一個在安裝,其他下載好的app也在排隊

 

0.  線程thread

    說起線程,不得不提起進程,

     線程,還真不好下定義,你要問十個人就會有十種答案,我就當線程就是一個可以執行的任務程式(比如上面圖片里的下載和安裝)。java里線程主要通過繼承java.lang.Thread類或實現java.lang.Runnable介面,其實Thread也是實現了Runnable介面的類,所有,線程還是圍繞著java.lang.Thread類擴展包裝,比如下麵要要說的線程池。核心類如下

 1 package java.lang;
 2 
 3 public class Thread implements Runnable {
 4 
 5     ....
 6     //線程的優先順序(類似於現實中的會員級別)
 7     /**
 8      * The minimum priority that a thread can have.
 9      */
10     public final static int MIN_PRIORITY = 1;
11 
12    /**
13      * The default priority that is assigned to a thread.
14      */
15     public final static int NORM_PRIORITY = 5;
16 
17     /**
18      * The maximum priority that a thread can have.
19      */
20     public final static int MAX_PRIORITY = 10;
21 
22     //構造線程的幾種方式
23     public Thread( );
24 
25     public Thread(Runnable target);
26 
27     public Thread(ThreadGroup group, Runnable target);
28 
29     public Thread(String name);
30 
31     public Thread(ThreadGroup group, String name);
32 
33     public Thread(Runnable target, String name);
34 
35     public Thread(ThreadGroup group, Runnable target, String name);
36 
37     public Thread(ThreadGroup group, Runnable target, String name,
38 
39                   long stackSize);
40 
41     //下麵的代碼和生命周期有關
42    public void start( );
43 
44     public void run( );
45 
46     public void stop( );   // Deprecated, do not use
47 
48     public void resume( );  // Deprecated, do not use
49 
50     public void suspend( );    // Deprecated, do not use
51 
52     public static void sleep(long millis);
53 
54     public static void sleep(long millis, int nanos);
55 
56     public boolean isAlive( );
57 
58     public void interrupt( );
59 
60     public boolean isInterrupted( );
61 
62     public static boolean interrupted( );
63 
64     public void join( ) throws InterruptedException;
65 
66     .........
67 
68 }
View Code

 

一個線程創建之後,總是處於其生命周期的4個狀態之一中。線程的狀態表明此線 程當前正在進行的活動,而線程的狀態是可以通過程式來進行控制的,就是說,可以對線程進行操作來改變狀態。這些操作包括啟動(start)、終止(stop)、睡眠(sleep)、掛起 (suspend)、恢復(resume)、等待(wait)和通知(notify)。每一個操作都對應了一個方法,這些方法是由java.lang提供的。

線程狀態在Java中是通過一個Thread的內部枚舉State標識的。

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }
View Code

 

創建狀態(Thread.State.NEW

     如果創建了一個線程而沒有啟動它,那麼,此線程就處於創建狀態。比如,下述語句執行 以後,使系統有了一個處於創建狀態的線程myThread:

Thread t= new ThreadClass();

其中,ThreadClass()是Thread的子類,而Thread是由java.lang提供的。

     處於創建狀態的線程還沒有獲得應有的資源,所以,這是一個空的線程。線程只有通過啟動後,系統才會為它分配資源。對處於創建狀態的線程可以進行兩種操作:一是啟動 (start)操作,使其進入可運行狀態,二是終止(stop)操作,使其進入消亡狀態。如果進入到消 亡狀態,那麼,此後這個線程就不能進入其他狀態,也就是說,它不再存在了。

     start方法是對應啟動操作的方法,其具體功能是為線程分配必要的系統資源;將線程設置為可運行狀態,從而可以使系統調度這個線程。 

    通過調用t.start()啟動一個線程,使該線程進入可運行(Thread.State.RUNNABLE)的狀態。

    由JVM的決定去調度(Scheduler) 在可運行狀態(Runnable)下的線程,使該線程處於運行 (Running) 狀態,由於JVM的調度會出現不可控性,即不是優先順序高的先被調用,可能先調用,也可能後調用的的情況。運行狀態(Running)下,調用禮讓yield()方法,可以使線程回到可運行狀態(Runnable)下,再次JVM的調度(並不依賴優先順序)。

    線程執行完畢或異常退出會進入終止狀態(Thread.State.TERMINATED)。

其餘的還有幾個狀態:

Thread.State.BLOCKED

受阻塞並且正在等待監視器鎖的某一線程的線程狀態。處於受阻塞狀態的某一線程正在等待監視器鎖,以便進入一個同步的塊/方法,或者在調用 Object.wait 之後再次進入同步的塊/方法。

 Thread.State.WAITING

某一等待線程的線程狀態。某一線程因為調用下列方法之一而處於等待狀態:

不帶超時值的 Object.wait

不帶超時值的 Thread.join

LockSupport.park

處於等待狀態的線程正等待另一個線程,以執行特定操作。 例如,已經在某一對象上調用了 Object.wait() 的線程正等待另一個線程,以便在該對象上調用 Object.notify() 或 Object.notifyAll()。已經調用了 Thread.join() 的線程正在等待指定線程終止。

TIMED_WAITING具有指定等待時間的某一等待線程的線程狀態。某一線程因為調用以下帶有指定正等待時間的方法之一而處於定時等待狀態:

Thread.sleep

帶有超時值的 Object.wait

帶有超時值的 Thread.join

LockSupport.parkNanos

LockSupport.parkUntil

謹記: 在給定時間點上,一個線程只會處於一種狀態,狀態轉換圖


線程優先順序

java線程的優先順序用整數表示,取值範圍是1~10,Thread類有以下三個靜態常量:

static int MAX_PRIORITY

          線程可以具有的最高優先順序,取值為10。

static int MIN_PRIORITY

          線程可以具有的最低優先順序,取值為1。

static int NORM_PRIORITY

          分配給線程的預設優先順序,取值為5。

Thread類的setPriority()和getPriority()方法分別用來設置和獲取線程的優先順序。

每個線程都有預設的優先順序,主線程的預設優先順序為Thread.NORM_PRIORITY。

線程的優先順序有繼承關係,比如A線程中創建了B線程,那麼B將和A具有相同的優先順序。

JVM提供了10個線程優先順序,但與常見的操作系統都不能很好的映射。如果希望程式能移植到各個操作系統中,應該僅僅使用Thread類有以下三個靜態常量作為優先順序,這樣能保證同樣的優先順序採用了同樣的調度方式。

1.  阻塞隊列

BlockingQueue隊列是一種數據結構,它有兩個基本操作:在隊列尾部加人一個元素,和從隊列頭部移除一個元素就是說,隊列以一種先進先出的方式管理數據,如果你試圖向一個已經滿了的阻塞隊列中添加一個元素或者是從一個空的阻塞隊列中移除一個元素,將導致線程阻塞。

在多線程進行合作時,阻塞隊列是很有用的工具。工作者線程可以定期地把中間結果存到阻塞隊列中而其他工作者線程把中間結果取出併在將來修改它們。隊列會自動平衡負載。如果第一個線程集運行得比第二個慢,則第二個線程集在等待結果時就會阻塞。如果第一個線程集運行得快,那麼它將等待第二個線程集趕上來。

而BlockingQueue隊列也是一組數據集合,它繼承了Queue介面,而Queue介面繼承了Collection介面。

阻塞隊列提供的相關操作和特點


在java包"java.util.concurrent"中提供了若幹種隊列,大神給你寫好了

ArrayBlockingQueue

一個由數組結構組成的有界阻塞隊列
LinkedBlockingQueue 一個由鏈表結構組成的有界阻塞隊列
PriorityBlockingQueue 一個支持優先順序排序的無界阻塞隊列
DelayQueue 一個使用優先順序隊列實現的無界阻塞隊列

SynchronousQueue

一個不存儲元素的阻塞隊列

LinkedTransferQueue

一個由鏈表結構組成的無界阻塞隊列

LinkedBlockingDeque

一個由鏈表結構組成的雙向阻塞隊列

ArrayBlockingQueue

底層用數組實現的有界阻塞隊列,預設情況下不保證線程公平的訪問隊列(按照阻塞的先後順序訪問隊列),隊列可用的時候,阻塞的線程都可以爭奪隊列的訪問資格,當然也可以使用以下的構造方法創建一個公平的阻塞隊列。ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(10, true)。

偽代碼:

 public ArrayBlockingQueue(int capacity, boolean fair) {
     if (capacity <= 0)
         throw new IllegalArgumentException();
     this.items = new Object[capacity];
     lock = new ReentrantLock(fair);
     notEmpty = lock.newCondition();
     notFull =  lock.newCondition();
 }

(其實就是通過將ReentrantLock設置為true來 達到這種公平性的:即等待時間最長的線程會先操作)。用ReentrantLock condition 實現阻塞。

有界就是隊列的長度有限制,例如數組隊列,在構建的時候就指定了長度。無界就是可以無限地添加。

LinkedBlockingQueue

底層基於鏈表實現的有界阻塞隊列。此隊列的預設和最大長度為Integer.MAX_VALUE。此隊列按照先進先出的原則對元素進行排序。這個隊列的實現原理和ArrayBlockingQueue實現基本相同。也是採用ReentrantLock 控制併發,不同的是它使用兩個獨占鎖來控制消費和生產。即用takeLock和putlock,這樣的好處是消費者和生產者可以併發執行,對吞吐量有提升。

PriorityBlockingQueue

PriorityBlockingQueue是一個帶優先順序的隊列,而不是先進先出隊列。元素按優先順序順序被移除,該隊列也沒有上限(PriorityBlockingQueue是對 PriorityQueue的再次包裝,是基於堆數據結構的,而PriorityQueue是沒有容量限制的,與ArrayList一樣,所以在優先阻塞 隊列上put時是不會受阻的。雖然此隊列邏輯上是無界的,但是由於資源被耗盡,所以試圖執行添加操作可能會導致 OutOfMemoryError),但是如果隊列為空,那麼取元素的操作take就會阻塞,所以它的檢索操作take是受阻的。也是用ReentrantLock控制併發。

DelayQueue

DelayQueue是在PriorityQueue基礎上實現的,底層也是數組構造方法,是一個存放Delayed 元素的無界阻塞隊列,只有在延遲期滿時才能從中提取元素。該隊列的頭部是延遲期滿後保存時間最長的 Delayed 元素。如果延遲都還沒有期滿,則隊列沒有頭部,並且poll將返回null。當一個元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一個小於或等於零的值時,則出現期滿,poll就移除這個元素了。此隊列不允許使用 null 元素。

SynchronousQueue

一個沒有容量的隊列 ,不會存儲數據,每執行一次put就要執行一次take,否則就會阻塞。未使用鎖。通過cas實現,吞吐量異常高。內部採用的就是ArrayBlockingQueue的阻塞隊列,所以在功能上完全可以用ArrayBlockingQueue替換,但是SynchronousQueue是輕量級的,SynchronousQueue不具有任何內部容量,我們可以用來線上程間安全的交換單一元素。所以功能比較單一,優勢就在於輕量。

LinkedBlockingDeque

LinkedBlockingDeque是雙向鏈表實現的雙向併發阻塞隊列。該阻塞隊列同時支持FIFO和FILO兩種操作方式,即可以從隊列的頭和尾同時操作(插入/刪除);並且,該阻塞隊列是支持線程安全,當多線程競爭同一個資源時,某線程獲取到該資源之後,其它線程需要阻塞等待。此外,LinkedBlockingDeque還是可選容量的(防止過度膨脹),即可以指定隊列的容量。如果不指定,預設容量大小等於Integer.MAX_VALUE。

LinkedTransferQueue

jdk7才提供這個類,這個類實現了TransferQueue介面,也是基於鏈表的,對於所有給定的生產者都是先入先出的。與其他阻塞隊列的區別是:其他阻塞隊列,生產者生產數據,如果隊列沒有滿,放下數據就走,消費者獲取數據,看到有數據獲取數據就走。而LinkedTransferQueue生產者放數據的時候,如果此時消費者沒有獲取,則需阻塞等待直到有消費者過來獲取數據。有點類似SynchronousQueue,但是LinkedTransferQueue是被設計有容量的。LinkedTransferQueue 通過使用CAS來實現併發控制,是一個無界的安全隊列。其長度可以無限延伸,當然帶來的問題也是顯而易見的。

 

2.  線程池ThreadPool

線程池,可以理解為存放線程的容器。

既然可以通過new出線程,那為什麼要線程池呢,因為有以下優點:

(1)重用存在的線程,減少對象創建、消亡的開銷,性能佳。

(2)可有效控制最大併發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。

(3)提供定時執行、定期執行、單線程、併發數控制等功能。

而單獨建立線程(特別是項目組開發人員多的時候,各創建各自的線程),卻有以下缺點:

(1) 每次new Thread新建對象性能差。因為每次都會創建一個對象。這是既耗時又消耗資源的。

(2) 線程缺乏統一管理,可能會造成自鎖,或者是記憶體溢出。

(3)缺乏更多功能,如定時執行、定期執行、線程中斷。

 

Java通過Executors提供四種線程池,分別為:
newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
newScheduledThreadPool 創建一個定長線程池,支持定時及周期性任務執行。
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。

不過這幾種創建線程池方便,可隱藏了細節也不好,既然四種創建線程池最後是通過java.util.concurrent.ThreadPoolExecutor類構造,不如直接考察它,代碼如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

請務必搞懂方方法里的參數:

(1)int corePoolSize(核心線程數):

線程池新建線程的時候,如果當前線程總數小於corePoolSize,則新建的是核心線程,核心線程預設情況下會一直存活線上程池中;如果設置了 allowCoreThreadTimeOut 為 true,那麼核心線程如果不幹活的話,超過一定時間,就會被銷毀掉。

(2)int maximumPoolSize(線程池能容納的最大線程數量):

線程總數 = 核心線程數 + 非核心線程數。

(3)long keepAliveTime(非核心線程空閑存活時長):

非核心線程空閑時長超過該時長將會被回收

(4)TimeUnit unit 空閑線程的存活時間

在這裡表示的是時間的單位,比如說秒。

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小時
TimeUnit.MINUTES;           //分鐘
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //納秒

(5)BlockingQueue workQueue(任務隊列),就上面說的幾種隊列:

當所有的核心線程都在幹活時,新添加的任務會被添加到這個隊列中等待處理,如果隊列滿了,則新建非核心線程執行任務。常用的workQueue類型:

ArrayBlockingQueue:這裡表示接到新任務,如果沒有達到核心線程數,則新建核心線程執行任務,如果達到了,則入隊等候,如果隊列已滿,則新建非核心線程執行任務,又如果匯流排程數到了 maximumPoolSize,並且隊列也滿了,則發生錯誤。

LinkedBlockingQueue:這裡表示接到新任務,如果當前線程數小於核心線程數,則新建核心線程處理任務;如果當前線程數等於核心線程數,則進入隊列等待。

DelayQueue:這裡表示接到新任務,先入隊,達到了指定的延時時間,才執行任務。

SynchornousQueue:這裡表示接到新任務,直接交給線程處理,如果其他的線程都在工作,那就創建一個新的線程來處理這個任務。

(6).ThreadFactory threadFactory(線程工廠):

用來創建線程池中的線程。

(7).RejectedExecutionHandler handler(拒絕策略):

指的之超過了maximumPoolSize,無法再處理新的任務,就會直接拒絕,提供了以下 4 種策略:

AbortPolicy:預設策略,在拒絕任務時,會拋出RejectedExecutionException。

CallerRunsPolicy:只要線程池未關閉,該策略直接在調用者線程中,運行當前的被丟棄的任務。

DiscardOldestPolicy:該策略將丟棄最老的一個請求,也就是即將被執行的任務,並嘗試再次提交當前任務。

DiscardPolicy:該策略默默的丟棄無法處理的任務,不予任何處理

 附圖兩張:

找到ThreadPoolExecutor和BlockingQueue了嗎

3.  進入正題:下載和安裝

普及了0,1,2後,開始說正事,怕直接說多線程併發,隊列,任務,不好接受。

最後代碼如下:

package code.vivo;

import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;

/**
 * 
 * @author dongguangming
 * @describe "要更新的app url地址"
 * @date 2020年4月15日
 */
public class DownloadAppThread extends Thread {

      /**
       * app url
       */
     private String appUrl;
     /**
      * 下載存放的路徑
      */
     private String filePath;
     
     /**
      * 安裝軟體包的池
      */
     private ExecutorService installExecutorService;
     
     public DownloadAppThread(String appUrl, String filePath,ExecutorService executorService) {
        super();
        this.appUrl = appUrl;
        this.filePath = filePath;
        this.installExecutorService = executorService;
    }

    @Override
     public void run()
     {
        BufferedInputStream  bis = null;
        FileOutputStream  fos = null;
        HttpURLConnection conn = null;
         try
         {
             conn = getHttp();
             conn.connect();
             //
             bis = new BufferedInputStream(conn.getInputStream());
             fos = new FileOutputStream(filePath);
             int len = 0;
             byte[] buf = new byte[1024*8];
             
             while ((len = bis.read(buf)) != -1)
             {
                 fos.write(buf, 0, len);
             }
             System.out.println(appUrl+"下載完畢,要進入安裝狀態"); 
             //給安裝池發消息發一個消息
             //Message message = new Message(appUrl);
             //handler.sendMessage(message);
             this.installExecutorService.execute(new InstallAppThread(this.appUrl));
         }
         catch (Exception e)
         {
             e.getMessage();
         } finally {
            //關閉各種資源
            try {
                fos.close();
                bis.close();
                conn.disconnect();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
         }
     }

    /**
     * 連接app下載地址
     * @return
     * @throws IOException
     */
     public HttpURLConnection getHttp() throws IOException
     {
         URL url = null;
         if (appUrl != null)
         {
             url = new URL(appUrl);
         }
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
         conn.setReadTimeout(5000);
         conn.setRequestMethod("GET");

         return conn;
     }
}

package code.vivo;

/**
 * 
 * @author dongguangming
 * @describe "把下載好的app安裝"
 * @date 2020年4月15日
 */
public class InstallAppThread extends Thread {  
      
    private String appName;
    
    public InstallAppThread(String appName) {
        super();
        this.appName = appName;
    }

    public void run() {  
        System.out.println("安裝app:"+appName);  
    }  
}  

測試下載安裝(可以自定義線程池執行器,自定義工廠,自定義策略)

package code.vivo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 測試下載
 * @author dongguangming
 * @describe ""
 * @date 2020年4月15日
 */
public class DownLoadPool {

    // 要更新的app數量,我當時要升級更新的app數據
    private static final int APP_NUM = 38;

    public static void main(String[] args) throws InterruptedException {

        // 下載軟體包的池
        ExecutorService downloadPool = new ThreadPoolExecutor(3, 3, 10,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(APP_NUM),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        // 安裝軟體包的池(網速太快,雖然下載軟體apk很快,但安裝很緩慢,導致安裝包堆積)
        ExecutorService installPool = new ThreadPoolExecutor(1, 1, 10,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(APP_NUM),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        // 以下載圖片為例,生產上下載就是apk
        System.out.println("開始下載apk程式表完畢*******");
        for (int i = 1; i <= APP_NUM; i++) {
            downloadPool
                    .execute(new DownloadAppThread(
                            "http://static.jszg.edu.cn/public/184/Ig9l4KTB9m7xgMYnsdm5.jpg",
                            "f:\\download\\" + i + ".jpg", installPool));
            Thread.sleep(1000);
        }
        // pool.submit(runnable);
        //
        downloadPool.shutdown();
    }
}

執行效果圖



總結:好好學習,天天向上,寫也很累(有時候構思一篇文章兩三個小時很快就過去了,甚至一下午),也是思考的過程,看別人的文章像過天書一樣和聽別人講座,容易忘記(可以問問你的朋友同學同事等),要自己彙總,記憶會更深刻。

有時間了接著拆分!!!

擴展閱讀參考:

0.  https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html

1.  能有比官方更權威的嗎 https://docs.oracle.com/javase/tutorial/essential/concurrency/runthread.html

2. Java Concurrency in Practice http://jcip.net/,  國內已有翻譯版

3. http://gee.cs.oswego.edu/   你們用的併發包java.util.concurrent(簡稱JUC),便出於他和其他人之手,經常看到國內的碼農在拼命研究併發源碼(也有研究spring源碼的),不知道到什麼程度了,瞭解他這個人嗎。。。擔心就算看懂了代碼(我想只要是個碼農都能看得懂),也只是看懂了代碼,瞭解他的思想嗎,他當時是如何籌劃構思出來的,別人的技術,能學通50%(畢竟人家花了二十年循序漸進才做出來的庫和框架比如spring,http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html,是經驗和閱歷的成果),能靈活運用就算成功

******************************************************************************************************

4.  Java阻塞隊列實現原理分析  https://developer.51cto.com/art/201704/536821.htm

5.  聊聊併發(七)——Java 中的阻塞隊列 https://www.infoq.cn/article/java-blocking-queue/

6. Java線程池架構原理和源碼解析(ThreadPoolExecutor)https://mp.weixin.qq.com/s?__biz=MjM5NTg2NTU0Ng==&mid=214688037&idx=6&sn=d1c989e7f539732cda5ceaa6cabd8b29

7. Java線程池使用說明 https://mp.weixin.qq.com/s?__biz=MzIyNjA1MjAyNg==&mid=212879024&idx=1&sn=a05bf0b28846850a0730e5844f95126d

8. Java線程池---Executor框架源碼深度解析https://mp.weixin.qq.com/s?__biz=MzUyNDk2MTE1Mg==&mid=2247483663&idx=1&sn=cd57cf503c31eb6e4a423173520081f7

9. 深入源碼分析Java線程池的實現原理https://mp.weixin.qq.com/s/-89-CcDnSLBYy3THmcLEdQ

10. 深度解讀 Java 線程池設計思想及源碼實現 https://mp.weixin.qq.com/s?__biz=MzUxNDA1NDI3OA==&mid=2247486041&idx=1&sn=2bc12fd0b57bedb84eb11aca8a574306


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ES6 Promise應用: async, await 1. k2 var k1 = new Promise((resolve, reject) => { resolve('ok'); }) console.log(k1); async function k2() { } console.log(k ...
  • 關鍵字 meta(標簽)及 Keywords(關鍵詞),曾經在網頁中很重要的,但現在已經被很多搜索引擎完全忽略。但是,如果加上這個標簽也對網頁的綜合表現沒有壞處,不過,如果使用不恰當的話,對網頁非但沒有好處,還有欺詐的嫌疑。 <meta name="keywords" content="若夢,ech ...
  • 影響用戶訪問的最大部分是前端的頁面。網站的劃分一般為二:前端和後臺。我們可以理解成後臺是用來實現網站的功能的,比如:實現用戶註冊,用戶能夠為文章發表評論等等。而前端呢?其實應該是屬於功能的表現。 而我們建設網站的目的是什麼呢?不就是為了讓目標人群來訪問嗎?所以我們可以理解成前端才是真正和用戶接觸的。 ...
  • 本文基於Supermap平臺實現二維矢量數據的84轉火星。 步驟: 在iDesktop中把數據轉換成wgs84的地理坐標系 在iDesktop中,把數據轉成geojson。 註意格式要選擇utf-8,不然中文欄位會亂碼。 在nodejs下,使用projzh包把wgs84坐標轉成火星坐標。 //ind ...
  • 物聯網海量設備心跳註冊,脫網清除——多線程高併發互斥鎖落地 [toc] 1.應用背景 在物聯網應用場景中,需要維護很多個設備的連接,比如基於TCP socket通信的長連接,目的是為了獲取設備採集的信息,反向控制設備的數字開關或者模擬量。我們把這些TCP長連接都放入了基於線程安全的Concurren ...
  • BUAA OO 第二單元總結 Part 1 設計策略 這三次作業採用了 主線程獲取請求,多級調度器逐級分派,電梯模擬運行的策略 。具體來說,主線程實例化 類,通過阻塞讀取方式獲得請求 ,之後將請求分配給調度器 ,調度器負責處理請求(既可以自己處理,也可以分配給其他子調度器處理),每一個電梯與一個 綁 ...
  • 北航OO(2020)第二單元博客作業 [TOC] 設計策略分析(多線程視角) 本單元的三次作業中,我採用了相似的策略:採用輸入線程與電梯線程通過線程安全的調度器進行交互的方式。這種方式基本屬於生產者 消費者模式。在調度器的設計方面,我主要採用synchronized關鍵字結合wait和notify方 ...
  • 前言 這是一個基於中小型企業或團隊的架構設計。 不考慮大廠。有充分的理由相信,大廠有絕對的實力來搭建一個相當複雜的環境。 中小型企業或團隊是個什麼樣子? 開發團隊人員配置不全,部分人員身兼開發過程上下游的數個職責; 沒有專職的維護人員,或者維護人員實力不足以完全掌控生產和開發環境。 這種情況下,過於 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...