精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

新手也能看懂的線程池學習總結

開發(fā) 架構
池化技術相比大家已經屢見不鮮了,線程池、數據庫連接池、Http 連接池等等都是對這個思想的應用。池化技術的思想主要是為了減少每次獲取資源的消耗,提高對資源的利用率。

 [[282682]]

一 使用線程池的好處

池化技術相比大家已經屢見不鮮了,線程池、數據庫連接池、Http 連接池等等都是對這個思想的應用。池化技術的思想主要是為了減少每次獲取資源的消耗,提高對資源的利用率。

線程池提供了一種限制和管理資源(包括執(zhí)行一個任務)。每個線程池還維護一些基本統(tǒng)計信息,例如已完成任務的數量。

這里借用《Java 并發(fā)編程的藝術》提到的來說一下使用線程池的好處:

  • 降低資源消耗。通過重復利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
  • 提高響應速度。當任務到達時,任務可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
  • 提高線程的可管理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一的分配,調優(yōu)和監(jiān)控。

二 Executor 框架

2.1 簡介

Executor 框架是 Java5 之后引進的,在 Java 5 之后,通過 Executor 來啟動線程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用線程池實現,節(jié)約開銷)外,還有關鍵的一點:有助于避免 this 逃逸問題。

補充:this 逃逸是指在構造函數返回之前其他線程就持有該對象的引用. 調用尚未構造完全的對象的方法可能引發(fā)令人疑惑的錯誤。

Executor 框架不僅包括了線程池的管理,還提供了線程工廠、隊列以及拒絕策略等,Executor 框架讓并發(fā)編程變得更加簡單。

2.2 Executor 框架結構(主要由三大部分組成)

1) 任務(Runnable /Callable)

執(zhí)行任務需要實現的 Runnable 接口 或 Callable接口。Runnable 接口或 Callable 接口實現類都可以被 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 執(zhí)行。

2) 任務的執(zhí)行(Executor)

如下圖所示,包括任務執(zhí)行機制的核心接口 Executor ,以及繼承自 Executor 接口的 ExecutorService 接口。ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 這兩個關鍵類實現了 ExecutorService 接口。

這里提了很多底層的類關系,但是,實際上我們需要更多關注的是 ThreadPoolExecutor 這個類,這個類在我們實際使用線程池的過程中,使用頻率還是非常高的。

注意: 通過查看 ScheduledThreadPoolExecutor 源代碼我們發(fā)現 ScheduledThreadPoolExecutor 實際上是繼承了 ThreadPoolExecutor 并實現了 ScheduledExecutorService ,而 ScheduledExecutorService 又實現了 ExecutorService,正如我們下面給出的類關系圖顯示的一樣。

ThreadPoolExecutor 類描述:

  1. //AbstractExecutorService實現了ExecutorService接口 
  2. public class ThreadPoolExecutor extends AbstractExecutorService 

ScheduledThreadPoolExecutor 類描述:

  1. //ScheduledExecutorService實現了ExecutorService接口 
  2. public class ScheduledThreadPoolExecutor 
  3.         extends ThreadPoolExecutor 
  4.         implements ScheduledExecutorService 

 

 

任務的執(zhí)行相關接口

3) 異步計算的結果(Future)

Future 接口以及 Future 接口的實現類 FutureTask 類都可以代表異步計算的結果。

當我們把 Runnable接口 或 Callable 接口 的實現類提交給 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 執(zhí)行。(調用 submit() 方法時會返回一個 FutureTask 對象)

2.3 Executor 框架的使用示意圖

 


Executor 框架的使用示意圖

 

 

主線程首先要創(chuàng)建實現 Runnable 或者 Callable 接口的任務對象。

把創(chuàng)建完成的實現 Runnable/Callable接口的 對象直接交給 ExecutorService 執(zhí)行:ExecutorService.execute(Runnable command))或者也可以把 Runnable 對象或Callable 對象提交給 ExecutorService 執(zhí)行(ExecutorService.submit(Runnable task)或ExecutorService.submit(Callable task))。

如果執(zhí)行 ExecutorService.submit(…),ExecutorService 將返回一個實現Future接口的對象(我們剛剛也提到過了執(zhí)行 execute()方法和 submit()方法的區(qū)別,submit()會返回一個 FutureTask 對象)。由于 FutureTask 實現了 Runnable,我們也可以創(chuàng)建 FutureTask,然后直接交給 ExecutorService 執(zhí)行。

最后,主線程可以執(zhí)行 FutureTask.get()方法來等待任務執(zhí)行完成。主線程也可以執(zhí)行FutureTask.cancel(boolean mayInterruptIfRunning)來取消此任務的執(zhí)行。

三 (重要)ThreadPoolExecutor 類簡單介紹

線程池實現類 ThreadPoolExecutor 是 Executor 框架最核心的類。

3.1 ThreadPoolExecutor 類分析

ThreadPoolExecutor 類中提供的四個構造方法。我們來看最長的那個,其余三個都是在這個構造方法的基礎上產生(其他幾個構造方法說白點都是給定某些默認參數的構造方法比如默認制定拒絕策略是什么),這里就不貼代碼講了,比較簡單。

  1. /** 
  2.      * 用給定的初始參數創(chuàng)建一個新的ThreadPoolExecutor。 
  3.      */ 
  4.     public ThreadPoolExecutor(int corePoolSize,//線程池的核心線程數量 
  5.                               int maximumPoolSize,//線程池的最大線程數 
  6.                               long keepAliveTime,//當線程數大于核心線程數時,多余的空閑線程存活的最長時間 
  7.                               TimeUnit unit,//時間單位 
  8.                               BlockingQueue<Runnable> workQueue,//任務隊列,用來儲存等待執(zhí)行任務的隊列 
  9.                               ThreadFactory threadFactory,//線程工廠,用來創(chuàng)建線程,一般默認即可 
  10.                               RejectedExecutionHandler handler//拒絕策略,當提交的任務過多而不能及時處理時,我們可以定制策略來處理任務 
  11.                                ) { 
  12.         if (corePoolSize < 0 || 
  13.             maximumPoolSize <= 0 || 
  14.             maximumPoolSize < corePoolSize || 
  15.             keepAliveTime < 0) 
  16.             throw new IllegalArgumentException(); 
  17.         if (workQueue == null || threadFactory == null || handler == null
  18.             throw new NullPointerException(); 
  19.         this.corePoolSize = corePoolSize; 
  20.         this.maximumPoolSize = maximumPoolSize; 
  21.         this.workQueue = workQueue; 
  22.         this.keepAliveTime = unit.toNanos(keepAliveTime); 
  23.         this.threadFactory = threadFactory; 
  24.         this.handler = handler; 
  25.     } 

下面這些對創(chuàng)建 非常重要,在后面使用線程池的過程中你一定會用到!所以,務必拿著小本本記清楚。

  • ThreadPoolExecutor 3 個最重要的參數:
  • corePoolSize : 核心線程數線程數定義了最小可以同時運行的線程數量。
  • maximumPoolSize : 當隊列中存放的任務達到隊列容量的時候,當前可以同時運行的線程數量變?yōu)樽畲缶€程數。

workQueue: 當新任務來的時候會先判斷當前運行的線程數量是否達到核心線程數,如果達到的話,信任就會被存放在隊列中。

ThreadPoolExecutor其他常見參數:

  1. keepAliveTime:當線程池中的線程數量大于 corePoolSize 的時候,如果這時沒有新的任務提交,核心線程外的線程不會立即銷毀,而是會等待,直到等待的時間超過了 keepAliveTime才會被回收銷毀;
  2. unit : keepAliveTime 參數的時間單位。
  3. threadFactory :executor 創(chuàng)建新線程的時候會用到。
  4. handler :飽和策略。關于飽和策略下面單獨介紹一下。

下面這張圖可以加深你對線程池中各個參數的相互關系的理解(圖片來源:《Java性能調優(yōu)實戰(zhàn)》):

線程池各個參數的關系

 

ThreadPoolExecutor 飽和策略定義:

如果當前同時運行的線程數量達到最大線程數量并且隊列也已經被放滿了任時,ThreadPoolTaskExecutor 定義一些策略:

  • ThreadPoolExecutor.AbortPolicy:拋出 RejectedExecutionException來拒絕新任務的處理。
  • ThreadPoolExecutor.CallerRunsPolicy:調用執(zhí)行自己的線程運行任務。您不會任務請求。但是這種策略會降低對于新任務提交速度,影響程序的整體性能。另外,這個策略喜歡增加隊列容量。如果您的應用程序可以承受此延遲并且你不能任務丟棄任何一個任務請求的話,你可以選擇這個策略。
  • ThreadPoolExecutor.DiscardPolicy: 不處理新任務,直接丟棄掉。
  • ThreadPoolExecutor.DiscardOldestPolicy: 此策略將丟棄最早的未處理的任務請求。

舉個例子:

Spring 通過 ThreadPoolTaskExecutor 或者我們直接通過 ThreadPoolExecutor 的構造函數創(chuàng)建線程池的時候,當我們不指定 RejectedExecutionHandler 飽和策略的話來配置線程池的時候默認使用的是 ThreadPoolExecutor.AbortPolicy。在默認情況下,ThreadPoolExecutor 將拋出 RejectedExecutionException 來拒絕新來的任務 ,這代表你將丟失對這個任務的處理。對于可伸縮的應用程序,建議使用 ThreadPoolExecutor.CallerRunsPolicy。當最大池被填滿時,此策略為我們提供可伸縮隊列。(這個直接查看 ThreadPoolExecutor 的構造函數源碼就可以看出,比較簡單的原因,這里就不貼代碼了。)

3.2 推薦使用 ThreadPoolExecutor 構造函數創(chuàng)建線程池

在《阿里巴巴 Java 開發(fā)手冊》“并發(fā)處理”這一章節(jié),明確指出線程資源必須通過線程池提供,不允許在應用中自行顯示創(chuàng)建線程。

為什么呢?

使用線程池的好處是減少在創(chuàng)建和銷毀線程上所消耗的時間以及系統(tǒng)資源開銷,解決資源不足的問題。如果不使用線程池,有可能會造成系統(tǒng)創(chuàng)建大量同類線程而導致消耗完內存或者“過度切換”的問題。

另外《阿里巴巴 Java 開發(fā)手冊》中強制線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 構造函數的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規(guī)則,規(guī)避資源耗盡的風險

Executors 返回線程池對象的弊端如下:

FixedThreadPool 和 SingleThreadExecutor :允許請求的隊列長度為 Integer.MAX_VALUE,可能堆積大量的請求,從而導致 OOM。

CachedThreadPool 和 ScheduledThreadPool :允許創(chuàng)建的線程數量為 Integer.MAX_VALUE ,可能會創(chuàng)建大量線程,從而導致 OOM。

方式一:通過ThreadPoolExecutor構造函數實現(推薦)

方式二:通過 Executor 框架的工具類 Executors 來實現我們可以創(chuàng)建三種類型的 ThreadPoolExecutor:

 

  • FixedThreadPool
  • SingleThreadExecutor
  • CachedThreadPool

對應 Executors 工具類中的方法如圖所示:

 

四 (重要)ThreadPoolExecutor 使用示例我們上面講解了 Executor框架以及 ThreadPoolExecutor 類,下面讓我們實戰(zhàn)一下,來通過寫一個 ThreadPoolExecutor 的小 Demo 來回顧上面的內容。

4.1 示例代碼:Runnable+ThreadPoolExecutor

首先創(chuàng)建一個 Runnable 接口的實現類(當然也可以是 Callable 接口,我們上面也說了兩者的區(qū)別。)

MyRunnable.java

  1. import java.util.Date
  2.  
  3. /** 
  4.  * 這是一個簡單的Runnable類,需要大約5秒鐘來執(zhí)行其任務。 
  5.  * @author shuang.kou 
  6.  */ 
  7. public class MyRunnable implements Runnable { 
  8.  
  9.     private String command; 
  10.  
  11.     public MyRunnable(String s) { 
  12.         this.command = s; 
  13.     } 
  14.  
  15.     @Override 
  16.     public void run() { 
  17.         System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date()); 
  18.         processCommand(); 
  19.         System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date()); 
  20.     } 
  21.  
  22.     private void processCommand() { 
  23.         try { 
  24.             Thread.sleep(5000); 
  25.         } catch (InterruptedException e) { 
  26.             e.printStackTrace(); 
  27.         } 
  28.     } 
  29.  
  30.     @Override 
  31.     public String toString() { 
  32.         return this.command; 
  33.     } 

編寫測試程序,我們這里以阿里巴巴推薦的使用 ThreadPoolExecutor 構造函數自定義參數的方式來創(chuàng)建線程池。

ThreadPoolExecutorDemo.java

  1. import java.util.concurrent.ArrayBlockingQueue; 
  2. import java.util.concurrent.ThreadPoolExecutor; 
  3. import java.util.concurrent.TimeUnit; 
  4.  
  5. public class ThreadPoolExecutorDemo { 
  6.  
  7.     private static final int CORE_POOL_SIZE = 5; 
  8.     private static final int MAX_POOL_SIZE = 10; 
  9.     private static final int QUEUE_CAPACITY = 100; 
  10.     private static final Long KEEP_ALIVE_TIME = 1L; 
  11.     public static void main(String[] args) { 
  12.  
  13.         //使用阿里巴巴推薦的創(chuàng)建線程池的方式 
  14.         //通過ThreadPoolExecutor構造函數自定義參數創(chuàng)建 
  15.         ThreadPoolExecutor executor = new ThreadPoolExecutor( 
  16.                 CORE_POOL_SIZE, 
  17.                 MAX_POOL_SIZE, 
  18.                 KEEP_ALIVE_TIME, 
  19.                 TimeUnit.SECONDS, 
  20.                 new ArrayBlockingQueue<>(QUEUE_CAPACITY), 
  21.                 new ThreadPoolExecutor.CallerRunsPolicy()); 
  22.  
  23.         for (int i = 0; i < 10; i++) { 
  24.             //創(chuàng)建WorkerThread對象(WorkerThread類實現了Runnable 接口) 
  25.             Runnable worker = new MyRunnable("" + i); 
  26.             //執(zhí)行Runnable 
  27.             executor.execute(worker); 
  28.         } 
  29.         //終止線程池 
  30.         executor.shutdown(); 
  31.         while (!executor.isTerminated()) { 
  32.         } 
  33.         System.out.println("Finished all threads"); 
  34.     } 

可以看到我們上面的代碼指定了:

  1. corePoolSize: 核心線程數為 5。
  2. maximumPoolSize :最大線程數 10
  3. keepAliveTime : 等待時間為 1L。
  4. unit: 等待時間的單位為 TimeUnit.SECONDS。
  5. workQueue:任務隊列為 ArrayBlockingQueue,并且容量為 100;
  6. handler:飽和策略為 CallerRunsPolicy。

Output:

  1. pool-1-thread-2 Start. Time = Tue Nov 12 20:59:44 CST 2019 
  2. pool-1-thread-5 Start. Time = Tue Nov 12 20:59:44 CST 2019 
  3. pool-1-thread-4 Start. Time = Tue Nov 12 20:59:44 CST 2019 
  4. pool-1-thread-1 Start. Time = Tue Nov 12 20:59:44 CST 2019 
  5. pool-1-thread-3 Start. Time = Tue Nov 12 20:59:44 CST 2019 
  6. pool-1-thread-5 EndTime = Tue Nov 12 20:59:49 CST 2019 
  7. pool-1-thread-3 EndTime = Tue Nov 12 20:59:49 CST 2019 
  8. pool-1-thread-2 EndTime = Tue Nov 12 20:59:49 CST 2019 
  9. pool-1-thread-4 EndTime = Tue Nov 12 20:59:49 CST 2019 
  10. pool-1-thread-1 EndTime = Tue Nov 12 20:59:49 CST 2019 
  11. pool-1-thread-2 Start. Time = Tue Nov 12 20:59:49 CST 2019 
  12. pool-1-thread-1 Start. Time = Tue Nov 12 20:59:49 CST 2019 
  13. pool-1-thread-4 Start. Time = Tue Nov 12 20:59:49 CST 2019 
  14. pool-1-thread-3 Start. Time = Tue Nov 12 20:59:49 CST 2019 
  15. pool-1-thread-5 Start. Time = Tue Nov 12 20:59:49 CST 2019 
  16. pool-1-thread-2 EndTime = Tue Nov 12 20:59:54 CST 2019 
  17. pool-1-thread-3 EndTime = Tue Nov 12 20:59:54 CST 2019 
  18. pool-1-thread-4 EndTime = Tue Nov 12 20:59:54 CST 2019 
  19. pool-1-thread-5 EndTime = Tue Nov 12 20:59:54 CST 2019 
  20. pool-1-thread-1 EndTime = Tue Nov 12 20:59:54 CST 2019 

4.2 線程池原理分析

承接 5.1 節(jié),我們通過代碼輸出結果可以看出:線程池每次會同時執(zhí)行 5 個任務,這 5 個任務執(zhí)行完之后,剩余的 5 個任務才會被執(zhí)行。 大家可以先通過上面講解的內容,分析一下到底是咋回事?(自己獨立思考一會)

現在,我們就分析上面的輸出內容來簡單分析一下線程池原理。

**為了搞懂線程池的原理,我們需要首先分析一下 execute方法。**在 5.1 節(jié)中的 Demo 中我們使用 executor.execute(worker)來提交一個任務到線程池中去,這個方法非常重要,下面我們來看看它的源碼:

  1. // 存放線程池的運行狀態(tài) (runState) 和線程池內有效線程的數量 (workerCount) 
  2.    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 
  3.  
  4.     private static int workerCountOf(int c) { 
  5.         return c & CAPACITY; 
  6.     } 
  7.  
  8.     private final BlockingQueue<Runnable> workQueue; 
  9.  
  10.     public void execute(Runnable command) { 
  11.         // 如果任務為null,則拋出異常。 
  12.         if (command == null
  13.             throw new NullPointerException(); 
  14.         // ctl 中保存的線程池當前的一些狀態(tài)信息 
  15.         int c = ctl.get(); 
  16.  
  17.         //  下面會涉及到 3 步 操作 
  18.         // 1.首先判斷當前線程池中之行的任務數量是否小于 corePoolSize 
  19.         // 如果小于的話,通過addWorker(command, true)新建一個線程,并將任務(command)添加到該線程中;然后,啟動該線程從而執(zhí)行任務。 
  20.         if (workerCountOf(c) < corePoolSize) { 
  21.             if (addWorker(command, true)) 
  22.                 return
  23.             c = ctl.get(); 
  24.         } 
  25.         // 2.如果當前之行的任務數量大于等于 corePoolSize 的時候就會走到這里 
  26.         // 通過 isRunning 方法判斷線程池狀態(tài),線程池處于 RUNNING 狀態(tài)才會被并且隊列可以加入任務,該任務才會被加入進去 
  27.         if (isRunning(c) && workQueue.offer(command)) { 
  28.             int recheck = ctl.get(); 
  29.             // 再次獲取線程池狀態(tài),如果線程池狀態(tài)不是 RUNNING 狀態(tài)就需要從任務隊列中移除任務,并嘗試判斷線程是否全部執(zhí)行完畢。同時執(zhí)行拒絕策略。 
  30.             if (!isRunning(recheck) && remove(command)) 
  31.                 reject(command); 
  32.                 // 如果當前線程池為空就新創(chuàng)建一個線程并執(zhí)行。 
  33.             else if (workerCountOf(recheck) == 0) 
  34.                 addWorker(nullfalse); 
  35.         } 
  36.         //3. 通過addWorker(command, false)新建一個線程,并將任務(command)添加到該線程中;然后,啟動該線程從而執(zhí)行任務。 
  37.         //如果addWorker(command, false)執(zhí)行失敗,則通過reject()執(zhí)行相應的拒絕策略的內容。 
  38.         else if (!addWorker(command, false)) 
  39.             reject(command); 
  40.     } 

通過下圖可以更好的對上面這 3 步做一個展示,下圖是我為了省事直接從網上找到,原地址不明。

圖解線程池實現原理

 

現在,讓我們在回到 5.1 節(jié)我們寫的 Demo, 現在應該是不是很容易就可以搞懂它的原理了呢?

沒搞懂的話,也沒關系,可以看看我的分析:

我們在代碼中模擬了 10 個任務,我們配置的核心線程數為 5 、等待隊列容量為 100 ,所以每次只可能存在 5 個任務同時執(zhí)行,剩下的 5 個任務會被放到等待隊列中去。當前的 5 個任務之行完成后,才會之行剩下的 5 個任務。

4.3 幾個常見的對比

4.3.1 Runnable vs Callable

Runnable自 Java 1.0 以來一直存在,但Callable僅在 Java 1.5 中引入,目的就是為了來處理Runnable不支持的用例。Runnable 接口不會返回結果或拋出檢查異常,但是**Callable 接口**可以。所以,如果任務不需要返回結果或拋出異常推薦使用 Runnable 接口,這樣代碼看起來會更加簡潔。

工具類 Executors 可以實現 Runnable 對象和 Callable 對象之間的相互轉換。(Executors.callable(Runnable task)或 Executors.callable(Runnable task,Object resule))。

Runnable.java

  1. @FunctionalInterface 
  2. public interface Runnable { 
  3.    /** 
  4.     * 被線程執(zhí)行,沒有返回值也無法拋出異常 
  5.     */ 
  6.     public abstract void run(); 

Callable.java

  1. @FunctionalInterface 
  2. public interface Callable<V> { 
  3.     /** 
  4.      * 計算結果,或在無法這樣做時拋出異常。 
  5.      * @return 計算得出的結果 
  6.      * @throws 如果無法計算結果,則拋出異常 
  7.      */ 
  8.     V call() throws Exception; 

4.3.2 execute() vs submit()

  1. execute()方法用于提交不需要返回值的任務,所以無法判斷任務是否被線程池執(zhí)行成功與否;
  2. submit()方法用于提交需要返回值的任務。線程池會返回一個 Future 類型的對象,通過這個 Future 對象可以判斷任務是否執(zhí)行成功,并且可以通過 Future 的 get()方法來獲取返回值,get()方法會阻塞當前線程直到任務完成,而使用 get(long timeout,TimeUnit unit)方法則會阻塞當前線程一段時間后立即返回,這時候有可能任務沒有執(zhí)行完。

我們以**AbstractExecutorService**接口中的一個 submit 方法為例子來看看源代碼:

  1. public Future<?> submit(Runnable task) { 
  2.         if (task == null) throw new NullPointerException(); 
  3.         RunnableFuture<Void> ftask = newTaskFor(task, null); 
  4.         execute(ftask); 
  5.         return ftask; 
  6.     } 

上面方法調用的 newTaskFor 方法返回了一個 FutureTask 對象。

  1. protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { 
  2.         return new FutureTask<T>(runnable, value); 
  3.     } 

我們再來看看execute()方法:

  1. public void execute(Runnable command) { 
  2.       ... 
  3.     } 

4.3.3 shutdown()VSshutdownNow()

shutdown() :關閉線程池,線程池的狀態(tài)變?yōu)?SHUTDOWN。線程池不再接受新任務了,但是隊列里的任務得執(zhí)行完畢。

shutdownNow() :關閉線程池,線程的狀態(tài)變?yōu)?STOP。線程池會終止當前正在運行的任務,并停止處理排隊的任務并返回正在等待執(zhí)行的 List。

4.3.2 isTerminated() VS isShutdown()

  • isShutDown 當調用 shutdown() 方法后返回為 true。
  • isTerminated 當調用 shutdown() 方法后,并且所有提交的任務完成后返回為 true

4.4 加餐:Callable+ThreadPoolExecutor示例代碼

MyCallable.java

  1. import java.util.concurrent.Callable; 
  2.  
  3. public class MyCallable implements Callable<String> { 
  4.     @Override 
  5.     public String call() throws Exception { 
  6.         Thread.sleep(1000); 
  7.         //返回執(zhí)行當前 Callable 的線程名字 
  8.         return Thread.currentThread().getName(); 
  9.     } 

CallableDemo.java

  1. import java.util.ArrayList; 
  2. import java.util.Date
  3. import java.util.List; 
  4. import java.util.concurrent.ArrayBlockingQueue; 
  5. import java.util.concurrent.Callable; 
  6. import java.util.concurrent.ExecutionException; 
  7. import java.util.concurrent.Future; 
  8. import java.util.concurrent.ThreadPoolExecutor; 
  9. import java.util.concurrent.TimeUnit; 
  10.  
  11. public class CallableDemo { 
  12.  
  13.     private static final int CORE_POOL_SIZE = 5; 
  14.     private static final int MAX_POOL_SIZE = 10; 
  15.     private static final int QUEUE_CAPACITY = 100; 
  16.     private static final Long KEEP_ALIVE_TIME = 1L; 
  17.  
  18.     public static void main(String[] args) { 
  19.  
  20.         //使用阿里巴巴推薦的創(chuàng)建線程池的方式 
  21.         //通過ThreadPoolExecutor構造函數自定義參數創(chuàng)建 
  22.         ThreadPoolExecutor executor = new ThreadPoolExecutor( 
  23.                 CORE_POOL_SIZE, 
  24.                 MAX_POOL_SIZE, 
  25.                 KEEP_ALIVE_TIME, 
  26.                 TimeUnit.SECONDS, 
  27.                 new ArrayBlockingQueue<>(QUEUE_CAPACITY), 
  28.                 new ThreadPoolExecutor.CallerRunsPolicy()); 
  29.  
  30.         List<Future<String>> futureList = new ArrayList<>(); 
  31.         Callable<String> callable = new MyCallable(); 
  32.         for (int i = 0; i < 10; i++) { 
  33.             //提交任務到線程池 
  34.             Future<String> future = executor.submit(callable); 
  35.             //將返回值 future 添加到 list,我們可以通過 future 獲得 執(zhí)行 Callable 得到的返回值 
  36.             futureList.add(future); 
  37.         } 
  38.         for (Future<String> fut : futureList) { 
  39.             try { 
  40.                 System.out.println(new Date() + "::" + fut.get()); 
  41.             } catch (InterruptedException | ExecutionException e) { 
  42.                 e.printStackTrace(); 
  43.             } 
  44.         } 
  45.         //關閉線程池 
  46.         executor.shutdown(); 
  47.     } 

Output:

  1. Wed Nov 13 13:40:41 CST 2019::pool-1-thread-1 
  2. Wed Nov 13 13:40:42 CST 2019::pool-1-thread-2 
  3. Wed Nov 13 13:40:42 CST 2019::pool-1-thread-3 
  4. Wed Nov 13 13:40:42 CST 2019::pool-1-thread-4 
  5. Wed Nov 13 13:40:42 CST 2019::pool-1-thread-5 
  6. Wed Nov 13 13:40:42 CST 2019::pool-1-thread-3 
  7. Wed Nov 13 13:40:43 CST 2019::pool-1-thread-2 
  8. Wed Nov 13 13:40:43 CST 2019::pool-1-thread-1 
  9. Wed Nov 13 13:40:43 CST 2019::pool-1-thread-4 
  10. Wed Nov 13 13:40:43 CST 2019::pool-1-thread-5 

##五 幾種常見的線程池詳解

5.1 FixedThreadPool

5.1.1 介紹

FixedThreadPool 被稱為可重用固定線程數的線程池。通過 Executors 類中的相關源代碼來看一下相關實現:

  1. /** 
  2.     * 創(chuàng)建一個可重用固定數量線程的線程池 
  3.     */ 
  4.    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { 
  5.        return new ThreadPoolExecutor(nThreads, nThreads, 
  6.                                      0L, TimeUnit.MILLISECONDS, 
  7.                                      new LinkedBlockingQueue<Runnable>(), 
  8.                                      threadFactory); 
  9.    } 

另外還有一個 FixedThreadPool 的實現方法,和上面的類似,所以這里不多做闡述:

  1. public static ExecutorService newFixedThreadPool(int nThreads) { 
  2.         return new ThreadPoolExecutor(nThreads, nThreads, 
  3.                                       0L, TimeUnit.MILLISECONDS, 
  4.                                       new LinkedBlockingQueue<Runnable>()); 
  5.     } 

從上面源代碼可以看出新創(chuàng)建的 FixedThreadPool 的 corePoolSize 和 maximumPoolSize 都被設置為 nThreads,這個 nThreads 參數是我們使用的時候自己傳遞的。

5.1.2 執(zhí)行任務過程介紹

FixedThreadPool 的 execute() 方法運行示意圖(該圖片來源:《Java 并發(fā)編程的藝術》):

FixedThreadPool的execute()方法運行示意圖

 

上圖說明:

  • 如果當前運行的線程數小于 corePoolSize, 如果再來新任務的話,就創(chuàng)建新的線程來執(zhí)行任務;
  • 當前運行的線程數等于 corePoolSize 后, 如果再來新任務的話,會將任務加入 LinkedBlockingQueue;
  • 線程池中的線程執(zhí)行完 手頭的任務后,會在循環(huán)中反復從 LinkedBlockingQueue 中獲取任務來執(zhí)行;

5.1.3 為什么不推薦使用FixedThreadPool?

FixedThreadPool 使用無界隊列 LinkedBlockingQueue(隊列的容量為 Intger.MAX_VALUE)作為線程池的工作隊列會對線程池帶來如下影響 :

  1. 當線程池中的線程數達到 corePoolSize 后,新任務將在無界隊列中等待,因此線程池中的線程數不會超過 corePoolSize;
  2. 由于使用無界隊列時 maximumPoolSize 將是一個無效參數,因為不可能存在任務隊列滿的情況。所以,通過創(chuàng)建 FixedThreadPool的源碼可以看出創(chuàng)建的 FixedThreadPool 的 corePoolSize 和 maximumPoolSize 被設置為同一個值。
  3. 由于 1 和 2,使用無界隊列時 keepAliveTime 將是一個無效參數;
  4. 運行中的 FixedThreadPool(未執(zhí)行 shutdown()或 shutdownNow())不會拒絕任務,在任務比較多的時候會導致 OOM(內存溢出)。

5.2 SingleThreadExecutor 詳解

5.2.1 介紹

SingleThreadExecutor 是只有一個線程的線程池。下面看看SingleThreadExecutor 的實現:

  1. /** 
  2.      *返回只有一個線程的線程池 
  3.      */ 
  4.     public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { 
  5.         return new FinalizableDelegatedExecutorService 
  6.             (new ThreadPoolExecutor(1, 1, 
  7.                                     0L, TimeUnit.MILLISECONDS, 
  8.                                     new LinkedBlockingQueue<Runnable>(), 
  9.                                     threadFactory)); 
  10.     } 
  11.    public static ExecutorService newSingleThreadExecutor() { 
  12.         return new FinalizableDelegatedExecutorService 
  13.             (new ThreadPoolExecutor(1, 1, 
  14.                                     0L, TimeUnit.MILLISECONDS, 
  15.                                     new LinkedBlockingQueue<Runnable>())); 
  16.     } 

從上面源代碼可以看出新創(chuàng)建的 SingleThreadExecutor 的 corePoolSize 和 maximumPoolSize 都被設置為 1.其他參數和 FixedThreadPool 相同。

5.2.2 執(zhí)行任務過程介紹

SingleThreadExecutor 的運行示意圖(該圖片來源:《Java 并發(fā)編程的藝術》):

 

上圖說明;

如果當前運行的線程數少于 corePoolSize,則創(chuàng)建一個新的線程執(zhí)行任務;

當前線程池中有一個運行的線程后,將任務加入 LinkedBlockingQueue

線程執(zhí)行完當前的任務后,會在循環(huán)中反復從LinkedBlockingQueue 中獲取任務來執(zhí)行;

5.2.3 為什么不推薦使用FixedThreadPool?

SingleThreadExecutor 使用無界隊列 LinkedBlockingQueue 作為線程池的工作隊列(隊列的容量為 Intger.MAX_VALUE)。SingleThreadExecutor 使用無界隊列作為線程池的工作隊列會對線程池帶來的影響與 FixedThreadPool 相同。說簡單點就是可能會導致 OOM,

5.3 CachedThreadPool 詳解

5.3.1 介紹

CachedThreadPool 是一個會根據需要創(chuàng)建新線程的線程池。下面通過源碼來看看 CachedThreadPool 的實現:

  1. /** 
  2.     * 創(chuàng)建一個線程池,根據需要創(chuàng)建新線程,但會在先前構建的線程可用時重用它。 
  3.     */ 
  4.    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { 
  5.        return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 
  6.                                      60L, TimeUnit.SECONDS, 
  7.                                      new SynchronousQueue<Runnable>(), 
  8.                                      threadFactory); 
  9.    } 
  10.  
  11.    public static ExecutorService newCachedThreadPool() { 
  12.        return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 
  13.                                      60L, TimeUnit.SECONDS, 
  14.                                      new SynchronousQueue<Runnable>()); 
  15.    } 

CachedThreadPool 的corePoolSize 被設置為空(0),maximumPoolSize被設置為 Integer.MAX.VALUE,即它是無界的,這也就意味著如果主線程提交任務的速度高于 maximumPool 中線程處理任務的速度時,CachedThreadPool 會不斷創(chuàng)建新的線程。極端情況下,這樣會導致耗盡 cpu 和內存資源。

5.3.2 執(zhí)行任務過程介紹

CachedThreadPool 的 execute()方法的執(zhí)行示意圖(該圖片來源:《Java 并發(fā)編程的藝術》):

 

上圖說明:

首先執(zhí)行 SynchronousQueue.offer(Runnable task) 提交任務到任務隊列。如果當前 maximumPool 中有閑線程正在執(zhí)行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主線程執(zhí)行 offer 操作與空閑線程執(zhí)行的 poll 操作配對成功,主線程把任務交給空閑線程執(zhí)行,execute()方法執(zhí)行完成,否則執(zhí)行下面的步驟 2;

當初始 maximumPool 為空,或者 maximumPool 中沒有空閑線程時,將沒有線程執(zhí)行 SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。這種情況下,步驟 1 將失敗,此時 CachedThreadPool 會創(chuàng)建新線程執(zhí)行任務,execute 方法執(zhí)行完成;

5.3.3 為什么不推薦使用CachedThreadPool?

CachedThreadPool允許創(chuàng)建的線程數量為 Integer.MAX_VALUE ,可能會創(chuàng)建大量線程,從而導致 OOM。

六 ScheduledThreadPoolExecutor 詳解

ScheduledThreadPoolExecutor 主要用來在給定的延遲后運行任務,或者定期執(zhí)行任務。 這個在實際項目中基本不會被用到,所以對這部分大家只需要簡單了解一下它的思想。關于如何在Spring Boot 中 實現定時任務,可以查看這篇文章《5分鐘搞懂如何在Spring Boot中Schedule Tasks》。

6.1 簡介

ScheduledThreadPoolExecutor 使用的任務隊列 DelayQueue 封裝了一個PriorityQueue,PriorityQueue 會對隊列中的任務進行排序,執(zhí)行所需時間短的放在前面先被執(zhí)行(ScheduledFutureTask 的 time 變量小的先執(zhí)行),如果執(zhí)行所需時間相同則先提交的任務將被先執(zhí)行(ScheduledFutureTask 的 squenceNumber 變量小的先執(zhí)行)。

ScheduledThreadPoolExecutor 和 Timer 的比較:

  • Timer 對系統(tǒng)時鐘的變化敏感,ScheduledThreadPoolExecutor不是;
  • Timer 只有一個執(zhí)行線程,因此長時間運行的任務可以延遲其他任務。ScheduledThreadPoolExecutor 可以配置任意數量的線程。此外,如果你想(通過提供 ThreadFactory),你可以完全控制創(chuàng)建的線程;
  • 在TimerTask 中拋出的運行時異常會殺死一個線程,從而導致 Timer 死機:-( ...即計劃任務將不再運行。ScheduledThreadExecutor 不僅捕獲運行時異常,還允許您在需要時處理它們(通過重寫 afterExecute 方法ThreadPoolExecutor)。拋出異常的任務將被取消,但其他任務將繼續(xù)運行。

綜上,在 JDK1.5 之后,你沒有理由再使用 Timer 進行任務調度了。

備注: Quartz 是一個由 java 編寫的任務調度庫,由 OpenSymphony 組織開源出來。在實際項目開發(fā)中使用 Quartz 的還是居多,比較推薦使用 Quartz。因為 Quartz 理論上能夠同時對上萬個任務進行調度,擁有豐富的功能特性,包括任務調度、任務持久化、可集群化、插件等等。

6.2 運行機制

ScheduledThreadPoolExecutor運行機制

 

ScheduledThreadPoolExecutor 的執(zhí)行主要分為兩大部分:

當調用 ScheduledThreadPoolExecutor 的 scheduleAtFixedRate() 方法或者**scheduleWirhFixedDelay()** 方法時,會向 ScheduledThreadPoolExecutor 的 DelayQueue 添加一個實現了 RunnableScheduledFuture 接口的 ScheduledFutureTask 。

線程池中的線程從 DelayQueue 中獲取 ScheduledFutureTask,然后執(zhí)行任務。

ScheduledThreadPoolExecutor 為了實現周期性的執(zhí)行任務,對 ThreadPoolExecutor做了如下修改:

  • 使用 DelayQueue 作為任務隊列;
  • 獲取任務的方不同
  • 執(zhí)行周期任務后,增加了額外的處理

6.3 ScheduledThreadPoolExecutor 執(zhí)行周期任務的步驟

 

 


ScheduledThreadPoolExecutor執(zhí)行周期任務的步驟

 

 

  • 線程 1 從 DelayQueue 中獲取已到期的 ScheduledFutureTask(DelayQueue.take())。到期任務是指 ScheduledFutureTask的 time 大于等于當前系統(tǒng)的時間;
  • 線程 1 執(zhí)行這個 ScheduledFutureTask;
  • 線程 1 修改 ScheduledFutureTask 的 time 變量為下次將要被執(zhí)行的時間;
  • 線程 1 把這個修改 time 之后的 ScheduledFutureTask 放回 DelayQueue 中(DelayQueue.add())。

七 線程池大小確定

線程池數量的確定一直是困擾著程序員的一個難題,大部分程序員在設定線程池大小的時候就是隨心而定。我們并沒有考慮過這樣大小的配置是否會帶來什么問題,我自己就是這大部分程序員中的一個代表。

由于筆主對如何確定線程池大小也沒有什么實際經驗,所以,這部分內容參考了網上很多文章/書籍。

首先,可以肯定的一點是線程池大小設置過大或者過小都會有問題。合適的才是最好,貌似在 95 % 的場景下都是合適的。

如果閱讀過我的上一篇關于線程池的文章的話,你一定知道:

如果我們設置的線程池數量太小的話,如果同一時間有大量任務/請求需要處理,可能會導致大量的請求/任務在任務隊列中排隊等待執(zhí)行,甚至會出現任務隊列滿了之后任務/請求無法處理的情況,或者大量任務堆積在任務隊列導致 OOM。這樣很明顯是有問題的!CPU 根本沒有得到充分利用。

但是,如果我們設置線程數量太大,大量線程可能會同時在爭取 CPU 資源,這樣會導致大量的上下文切換,從而增加線程的執(zhí)行時間,影響了整體執(zhí)行效率。

上下文切換:

多線程編程中一般線程的個數都大于 CPU 核心的個數,而一個 CPU 核心在任意時刻只能被一個線程使用,為了讓這些線程都能得到有效執(zhí)行,CPU 采取的策略是為每個線程分配時間片并輪轉的形式。當一個線程的時間片用完的時候就會重新處于就緒狀態(tài)讓給其他線程使用,這個過程就屬于一次上下文切換。概括來說就是:當前任務在執(zhí)行完 CPU 時間片切換到另一個任務之前會先保存自己的狀態(tài),以便下次再切換回這個任務時,可以再加載這個任務的狀態(tài)。任務從保存到再加載的過程就是一次上下文切換。

上下文切換通常是計算密集型的。也就是說,它需要相當可觀的處理器時間,在每秒幾十上百次的切換中,每次切換都需要納秒量級的時間。所以,上下文切換對系統(tǒng)來說意味著消耗大量的 CPU 時間,事實上,可能是操作系統(tǒng)中時間消耗最大的操作。

Linux 相比與其他操作系統(tǒng)(包括其他類 Unix 系統(tǒng))有很多的優(yōu)點,其中有一項就是,其上下文切換和模式切換的時間消耗非常少。

有一個簡單并且適用面比較廣的公式:

  • CPU 密集型任務(N+1): 這種任務消耗的主要是 CPU 資源,可以將線程數設置為 N(CPU 核心數)+1,比 CPU 核心數多出來的一個線程是為了防止線程偶發(fā)的缺頁中斷,或者其它原因導致的任務暫停而帶來的影響。一旦任務暫停,CPU 就會處于空閑狀態(tài),而在這種情況下多出來的一個線程就可以充分利用 CPU 的空閑時間。
  • I/O 密集型任務(2N): 這種任務應用起來,系統(tǒng)會用大部分的時間來處理 I/O 交互,而線程在處理 I/O 的時間段內不會占用 CPU 來處理,這時就可以將 CPU 交出給其它線程使用。因此在 I/O 密集型任務的應用中,我們可以多配置一些線程,具體的計算方法是 2N。

八 參考《Java 并發(fā)編程的藝術》

Java Scheduler ScheduledExecutorService ScheduledThreadPoolExecutor Example[1]

java.util.concurrent.ScheduledThreadPoolExecutor Example[2]

ThreadPoolExecutor – Java Thread Pool Example[3]

責任編輯:武曉燕 來源: JavaGuide
相關推薦

2018-12-24 08:46:52

Kubernetes對象模型

2019-10-10 11:10:04

SpringBoot異步編程

2018-05-16 10:07:02

監(jiān)控報警系統(tǒng)

2017-02-22 15:04:52

2013-09-22 10:34:08

碼農機器學習算法

2019-03-26 11:15:34

AI機器學習人工智能

2020-02-15 17:16:05

Kubernetes容器

2025-06-26 03:12:00

2024-11-01 05:10:00

2017-11-02 12:08:56

2021-11-01 15:15:37

Context項目代碼

2020-11-16 16:38:30

人工智能AI

2025-02-17 13:00:00

ChatGPT大模型AI

2025-02-17 10:09:54

2018-03-06 10:38:23

云計算大數據人工智能

2022-07-04 08:31:42

GitOpsGit基礎設施

2020-01-21 10:16:15

Kubernetes教程容器

2020-12-01 09:03:22

分庫分表MySQL

2024-01-19 13:39:00

死鎖框架排查

2019-09-05 14:21:22

JavaNIOBIO
點贊
收藏

51CTO技術棧公眾號

精品视频一区二区三区| 自拍视频在线播放| 首页国产欧美久久| 久久黄色av网站| 中国免费黄色片| 北岛玲heyzo一区二区| 国产精品美女久久久久久| 97自拍视频| 欧美视频xxxx| 激情自拍一区| 一本色道久久88综合日韩精品 | 亚洲女色av| 亚洲欧美在线视频| 麻豆成人av| 精品人妻一区二区三区四区不卡| 性久久久久久| 欧美激情精品久久久久久蜜臀 | 日韩av无码一区二区三区不卡| 精品视频在线一区二区在线| 亚洲一区二区三区四区的| 五月天综合网| 天堂在线中文字幕| 国产精品久久久久久av公交车| 亚洲影院久久精品| 在线观看一区二区三区三州| 免费在线毛片| 不卡的av中国片| 96国产粉嫩美女| 波多野结衣视频观看| 一本色道精品久久一区二区三区| 欧美美最猛性xxxxxx| 貂蝉被到爽流白浆在线观看| 免费成人网www| 亚洲国产中文字幕久久网| 91在线第一页| 国产电影一区二区| 5566中文字幕一区二区电影| 一区二区xxx| 日韩电影网站| 色综合久久66| 黄色片久久久久| 爱情电影社保片一区| 黑人巨大精品欧美一区二区免费| 欧日韩免费视频| 超免费在线视频| 一区二区三区在线免费| 男女爱爱视频网站| av网站导航在线观看免费| 国产精品盗摄一区二区三区| 亚洲蜜桃av| 天堂中文а√在线| 一区二区中文视频| 色一情一乱一乱一区91| 二区在线播放| 亚洲精品国产高清久久伦理二区| 九一免费在线观看| 手机在线免费观看av| 亚洲精品ww久久久久久p站| 中文字幕欧美日韩一区二区三区| 麻豆免费在线视频| 亚洲欧美另类在线| 青春草国产视频| 中文在线8资源库| 一本久久综合亚洲鲁鲁五月天| 国产精品天天av精麻传媒| 久久婷婷五月综合色丁香| 3atv一区二区三区| 国产精品欧美性爱| 天堂网av成人| 中文字幕亚洲欧美日韩高清| 免费在线观看a级片| 欧美三区不卡| 5252色成人免费视频| 天堂网视频在线| 蜜臀av性久久久久蜜臀aⅴ流畅 | 91麻豆国产在线| 国产真实乱对白精彩久久| caoporn国产精品免费公开| 国产香蕉在线观看| 国产日韩亚洲欧美综合| 一区二区在线中文字幕电影视频| 亚洲区欧洲区| 日韩欧美在线一区| 日韩欧美国产片| 国产毛片久久久| 伊人伊人伊人久久| 欧美成人一二三区| 久久婷婷激情| 91免费在线视频网站| 理论片中文字幕| 国产欧美一区二区精品久导航| 在线视频亚洲自拍| 日本午夜大片a在线观看| 欧美美女直播网站| 亚洲黄色在线网站| 羞羞色午夜精品一区二区三区| 午夜免费久久久久| 国产精品久久久久久久一区二区 | 精品国产免费人成电影在线观...| 久久国产精品高清一区二区三区| 亚洲九九爱视频| 欧美成人免费高清视频| 精品午夜av| 国产亚洲精品一区二区| 免费视频网站www| 日韩电影免费一区| 国内一区二区三区在线视频| 日本高清视频在线播放| 欧美天堂在线观看| 无码人妻丰满熟妇区毛片蜜桃精品| 久久99免费视频| 久久久久久久国产精品| 国产又粗又猛又色又| 久久网站热最新地址| 成人毛片100部免费看| 成人在线爆射| 亚洲精品久久久久久久久久久久| 内射一区二区三区| 母乳一区在线观看| 国内外成人免费视频| 亚洲淫性视频| 欧美精品乱人伦久久久久久| 蜜桃精品一区二区| 亚洲人成毛片在线播放女女| 亚洲free性xxxx护士白浆| 国产在线观看高清视频| 精品国产91久久久久久老师| 又大又长粗又爽又黄少妇视频| 天天av综合| 国产欧美一区二区三区视频 | 久久久久久91| 国产色视频在线| 中文字幕一区在线| 亚洲福利精品视频| 成人同人动漫免费观看| 国产不卡av在线免费观看| 头脑特工队2在线播放| 亚洲成a人v欧美综合天堂| 亚洲熟妇一区二区| 国内久久精品| 成人在线免费网站| 免费在线国产视频| 亚洲第一页自拍| 精品无码久久久久久久| 成a人片国产精品| 九色自拍视频在线观看| 欧美一区二区三区红桃小说| 午夜精品久久久久久久男人的天堂 | 国产又粗又爽又黄的视频| 日韩一区二区三区四区五区| 日韩在线视频一区| 国产一区二区三区黄片| 亚洲天堂2014| 中文字幕18页| 亚洲伦理一区| 日本不卡一区| 国产免费a视频| 免费在线观看av网站| 亚洲免费资源在线播放| www.桃色.com| 牛夜精品久久久久久久99黑人| 91在线视频一区| 伊人222成人综合网| 亚洲国产精品久久久| 国产精品久久久久久99| 日本一区二区三级电影在线观看| 爱情岛论坛亚洲首页入口章节| 99精品电影| 成人在线观看91| 周于希免费高清在线观看| 亚洲欧美日韩中文在线| ,亚洲人成毛片在线播放| 亚洲精品国产a久久久久久| 精品人妻一区二区三区日产| 日韩在线观看一区二区| 中文字幕99| 美女一区2区| 国产精品久久久久久久午夜 | 国产欧美一区二区精品性色超碰 | 国产精一区二区| 久久久久久久久国产| 青青青草原在线| 欧美精品欧美精品系列| 日韩女同强女同hd| 国产精品美日韩| 奇米777第四色| 日本亚洲欧美天堂免费| 成人午夜免费在线视频| 国产午夜一区| 亚洲一区精品电影| 欧美激情喷水| 麻豆成人在线看| 欧美黄色小说| 精品久久一区二区| 小泽玛利亚一区二区三区视频| 一区二区三区欧美亚洲| 久久久久久久毛片| 国产.精品.日韩.另类.中文.在线.播放| 欧美成人精品欧美一级乱| 自拍日韩欧美| 亚洲a∨一区二区三区| 国产ts一区| 91亚洲精品久久久| 天然素人一区二区视频| 欧美激情亚洲自拍| 高清全集视频免费在线| 一本久久综合亚洲鲁鲁| 污视频网站免费观看| 日韩欧美一区二区久久婷婷| 黄色av网站免费| 午夜欧美视频在线观看| 91嫩草|国产丨精品入口| 久久九九久精品国产免费直播| 日本一区二区免费视频| 国内精品伊人久久久久av影院 | 九九热在线视频播放| 亚洲女人****多毛耸耸8| 欧美激情亚洲色图| 久久综合久久99| 国产精品久久7| 极品人妻一区二区| 欧美性色综合| 一区二区精品视频| 精品国产一区二区三区噜噜噜 | 熟妇高潮一区二区| 精品一二三四在线| 精品久久久久久久无码| 国产欧美日韩综合一区在线播放 | 日韩av毛片| 久久影视电视剧免费网站清宫辞电视| 狠狠色伊人亚洲综合网站l| 亚洲电影免费观看高清完整版在线 | 五月婷婷欧美激情| 久久久精品国产免大香伊| 国产精品久久AV无码| av毛片久久久久**hd| 亚洲av无码一区东京热久久| 国产高清无密码一区二区三区| 一级做a免费视频| 久久国产剧场电影| 国模私拍视频在线观看| 久久精品99国产精品日本| 蜜桃免费在线视频| 免费的国产精品| 亚洲一级片免费| 蜜乳av一区二区| 在线观看国产福利| 深夜福利视频在线观看| www.日韩av| 中文字幕 亚洲一区| 91在线精品一区二区三区| 一级特级黄色片| 久久一夜天堂av一区二区三区| 三上悠亚ssⅰn939无码播放| 国产亚洲一区二区三区四区 | 九九视频精品全部免费播放| 久久精品综合一区| 国产一区二区精品福利地址| 日韩三级电影| 日韩av久操| 女同性恋一区二区| 伊人激情综合| 国产午夜福利100集发布| 亚洲女同同性videoxma| 韩国日本美国免费毛片| 精品无人码麻豆乱码1区2区 | 国产精品国产三级国产aⅴ无密码| 黄色国产在线视频| 国产亚洲精品7777| 91嫩草丨国产丨精品| 亚洲图片欧美一区| 欧美日韩偷拍视频| 五月综合激情婷婷六月色窝| 精品少妇一二三区| 色素色在线综合| 国产绿帽刺激高潮对白| 亚洲а∨天堂久久精品9966 | 久久亚洲精品国产亚洲老地址| 超碰在线免费公开| 45www国产精品网站| 色999韩欧美国产综合俺来也| 99国产在线视频| 免费短视频成人日韩| 久久免费视频2| 亚洲欧美不卡| 少妇丰满尤物大尺度写真| 久久久噜噜噜久久中文字幕色伊伊 | 亚洲色大成网站www久久九九| 久久久久亚洲av成人片| 日本丶国产丶欧美色综合| 精品人妻一区二区三区浪潮在线 | 经典一区二区三区| 水蜜桃av无码| **欧美大码日韩| 免费黄色网址在线| 91精品婷婷国产综合久久竹菊| 亚洲人午夜射精精品日韩| 久久视频在线免费观看| 色婷婷综合久久久中字幕精品久久 | 神马久久久久久久 | 日韩成人av影院| 国产喷白浆一区二区三区| 久久精品www人人爽人人| 91黄色免费看| 天堂在线视频免费观看| 久久婷婷国产麻豆91天堂| 日本成人三级电影| 成人黄色片视频网站| 久久麻豆精品| 成人精品视频一区二区| 成人av中文字幕| 日本老熟俱乐部h0930| 欧美亚洲国产一区二区三区va| 日本黄色三级视频| 欧美精品在线看| 日韩美香港a一级毛片| 久久久久久久久四区三区| 欧美日韩四区| 网站在线你懂的| 国产精品色呦呦| 日日夜夜操视频| 日韩成人久久久| h片视频在线观看| 96成人在线视频| 性欧美69xoxoxoxo| 亚洲天堂国产视频| 国产精品人人做人人爽人人添| 狠狠人妻久久久久久| 日韩电影中文字幕在线| 国内在线视频| 成人免费看片网站| 欧美日一区二区三区在线观看国产免| 欧美精品色视频| 亚洲欧美日韩久久| 国产又粗又猛又爽又黄视频 | 三级在线观看免费大全| 欧美日韩亚洲综合在线 欧美亚洲特黄一级| 日本一二三区在线视频| 欧美又大粗又爽又黄大片视频| 嫩草国产精品入口| 日韩欧美精品免费| 99视频在线观看一区三区| 日韩黄色在线视频| 日韩黄色在线免费观看| 国产伦理精品| 久久婷婷人人澡人人喊人人爽| 中文亚洲免费| 熟女少妇一区二区三区| 91精品办公室少妇高潮对白| 国产成人天天5g影院在线观看| 国产成人拍精品视频午夜网站| 少妇精品久久久| 9久久婷婷国产综合精品性色| 日本一区二区动态图| 黄色大全在线观看| 色妞一区二区三区| 国产一区二区av在线| 屁屁影院ccyy国产第一页| 成人激情免费电影网址| 福利一区二区三区四区| 日韩成人激情在线| 高清在线一区| 成人午夜免费在线视频| 91亚洲精品久久久蜜桃网站 | 少妇久久久久久被弄高潮| 日韩午夜在线观看| 国产免费拔擦拔擦8x在线播放 | 中文字幕一区二区三区乱码不卡| 精品女厕一区二区三区| 蜜桃视频在线观看视频| 91精品在线观看视频| 激情偷拍久久| 亚洲色图第四色| 精品少妇一区二区三区视频免付费 | 西西大胆午夜视频| 在线观看日韩高清av| 99热国产在线中文| 久久精品午夜一区二区福利| 美女免费视频一区二区| 久久国产在线观看| 亚洲天堂免费视频| 亚洲日本va中文字幕| 精品久久久久av| 亚洲精品乱码久久久久久| 日韩大胆人体| 91在线观看免费网站| 国产一区二区三区的电影| 黄色裸体一级片| 亚洲第一偷拍网| 国产精久久一区二区| 女人另类性混交zo| 亚洲自拍偷拍图区| 成人午夜电影在线观看| 国产精品v欧美精品v日韩精品| 日韩电影一区二区三区| 日本熟妇一区二区| 精品久久久91| 国产日产一区 | 一本色道久久亚洲综合精品蜜桃| 亚洲一区中文在线|