在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

細數線程池的10個坑

jf_ro2CN3Fa ? 來源:撿田螺的小男孩 ? 2023-06-16 10:11 ? 次閱讀

前言

日常開發中,為了更好管理線程資源,減少創建線程和銷毀線程的資源損耗,我們會使用線程池來執行一些異步任務。但是線程池使用不當,就可能會引發生產事故。今天跟大家聊聊線程池的10個坑。大家看完肯定會有幫助的~

線程池默認使用無界隊列,任務過多導致OOM

線程創建過多,導致OOM

共享線程池,次要邏輯拖垮主要邏輯

線程池拒絕策略的坑

Spring內部線程池的坑

使用線程池時,沒有自定義命名

線程池參數設置不合理

線程池異常處理的坑

使用完線程池忘記關閉

ThreadLocal與線程池搭配,線程復用,導致信息錯亂。

1.線程池默認使用無界隊列,任務過多導致OOM

JDK開發者提供了線程池的實現類,我們基于Executors組件,就可以快速創建一個線程池 。日常工作中,一些小伙伴為了開發效率,反手就用Executors新建個線程池。寫出類似以下的代碼:

publicclassNewFixedTest{

publicstaticvoidmain(String[]args){
ExecutorServiceexecutor=Executors.newFixedThreadPool(10);
for(inti=0;i{
try{
Thread.sleep(10000);
}catch(InterruptedExceptione){
//donothing
}
});
}
}
}

使用newFixedThreadPool創建的線程池,是會有坑的,它默認是無界的阻塞隊列,如果任務過多,會導致OOM問題。運行一下以上代碼,出現了OOM。

Exceptioninthread"main"java.lang.OutOfMemoryError:GCoverheadlimitexceeded
atjava.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
atjava.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
atcom.example.dto.NewFixedTest.main(NewFixedTest.java:14)

這是因為newFixedThreadPool使用了無界的阻塞隊列的LinkedBlockingQueue,如果線程獲取一個任務后,任務的執行時間比較長(比如,上面demo代碼設置了10秒),會導致隊列的任務越積越多,導致機器內存使用不停飆升, 最終出現OOM。

看下newFixedThreadPool的相關源碼,是可以看到一個無界的阻塞隊列的,如下:

//阻塞隊列是LinkedBlockingQueue,并且是使用的是無參構造函數
publicstaticExecutorServicenewFixedThreadPool(intnThreads){
returnnewThreadPoolExecutor(nThreads,nThreads,
0L,TimeUnit.MILLISECONDS,
newLinkedBlockingQueue());
}

//無參構造函數,默認最大容量是Integer.MAX_VALUE,相當于無界的阻塞隊列的了
publicLinkedBlockingQueue(){
this(Integer.MAX_VALUE);
}

因此,工作中,建議大家自定義線程池 ,并使用指定長度的阻塞隊列

2. 線程池創建線程過多,導致OOM

有些小伙伴說,既然Executors組件創建出的線程池newFixedThreadPool,使用的是無界隊列,可能會導致OOM。那么,Executors組件還可以創建別的線程池,如newCachedThreadPool,我們用它也不行嘛?

我們可以看下newCachedThreadPool的構造函數:

publicstaticExecutorServicenewCachedThreadPool(){
returnnewThreadPoolExecutor(0,Integer.MAX_VALUE,
60L,TimeUnit.SECONDS,
newSynchronousQueue());
}

它的最大線程數是Integer.MAX_VALUE。大家應該意識到使用它,可能會引發什么問題了吧。沒錯,如果創建了大量的線程也有可能引發OOM!

筆者在以前公司,遇到這么一個OOM問題:一個第三方提供的包,是直接使用new Thread實現多線程的。在某個夜深人靜的夜晚,我們的監控系統報警了。。。這個相關的業務請求瞬間特別多,監控系統告警OOM了。

所以我們使用線程池的時候,還要當心線程創建過多,導致OOM問題。大家盡量不要使用newCachedThreadPool,并且如果自定義線程池時,要注意一下最大線程數。

3. 共享線程池,次要邏輯拖垮主要邏輯

要避免所有的業務邏輯共享一個線程池。比如你用線程池A來做登錄異步通知,又用線程池A來做對賬。如下圖:

28fb8944-0bea-11ee-962d-dac502259ad0.png

如果對賬任務checkBillService響應時間過慢,會占據大量的線程池資源,可能直接導致沒有足夠的線程資源去執行loginNotifyService的任務,最后影響登錄。就這樣,因為一個次要服務,影響到重要的登錄接口,顯然這是絕對不允許的。因此,我們不能將所有的業務一鍋燉,都共享一個線程池,因為這樣做,風險太高了,猶如所有雞蛋放到一個籃子里。應當做線程池隔離

291bb8d6-0bea-11ee-962d-dac502259ad0.png

4. 線程池拒絕策略的坑,使用不當導致阻塞

我們知道線程池主要有四種拒絕策略,如下:

AbortPolicy: 丟棄任務并拋出RejectedExecutionException異常。(默認拒絕策略)

DiscardPolicy:丟棄任務,但是不拋出異常。

DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新嘗試執行任務。

CallerRunsPolicy:由調用方線程處理該任務。

如果線程池拒絕策略設置不合理,就容易有坑。我們把拒絕策略設置為DiscardPolicy或DiscardOldestPolicy并且在被拒絕的任務,Future對象調用get()方法,那么調用線程會一直被阻塞。

我們來看個demo:

publicclassDiscardThreadPoolTest{

publicstaticvoidmain(String[]args)throwsExecutionException,InterruptedException{
//一個核心線程,隊列最大為1,最大線程數也是1.拒絕策略是DiscardPolicy
ThreadPoolExecutorexecutorService=newThreadPoolExecutor(1,1,1L,TimeUnit.MINUTES,
newArrayBlockingQueue<>(1),newThreadPoolExecutor.DiscardPolicy());

Futuref1=executorService.submit(()->{
System.out.println("提交任務1");
try{
Thread.sleep(3000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
});

Futuref2=executorService.submit(()->{
System.out.println("提交任務2");
});

Futuref3=executorService.submit(()->{
System.out.println("提交任務3");
});

System.out.println("任務1完成"+f1.get());//等待任務1執行完畢
System.out.println("任務2完成"+f2.get());//等待任務2執行完畢
System.out.println("任務3完成"+f3.get());//等待任務3執行完畢

executorService.shutdown();//關閉線程池,阻塞直到所有任務執行完畢

}
}

運行結果:一直在運行中。。。

2940e796-0bea-11ee-962d-dac502259ad0.png

這是因為DiscardPolicy拒絕策略,是什么都沒做,源碼如下:

publicstaticclassDiscardPolicyimplementsRejectedExecutionHandler{
/**
*Createsa{@codeDiscardPolicy}.
*/
publicDiscardPolicy(){}

/**
*Doesnothing,whichhastheeffectofdiscardingtaskr.
*/
publicvoidrejectedExecution(Runnabler,ThreadPoolExecutore){
}
}

我們再來看看線程池 submit 的方法:

publicFuturesubmit(Runnabletask){
if(task==null)thrownewNullPointerException();
//把Runnable任務包裝為Future對象
RunnableFutureftask=newTaskFor(task,null);
//執行任務
execute(ftask);
//返回Future對象
returnftask;
}

publicFutureTask(Runnablerunnable,Vresult){
this.callable=Executors.callable(runnable,result);
this.state=NEW;//Future的初始化狀態是New
}

我們再來看看Future的get() 方法

//狀態大于COMPLETING,才會返回,要不然都會阻塞等待
publicVget()throwsInterruptedException,ExecutionException{
ints=state;
if(s<=?COMPLETING)
????????????s?=?awaitDone(false,?0L);
????????return?report(s);
????}
????
????FutureTask的狀態枚舉
????private?static?final?int?NEW??????????=?0;
????private?static?final?int?COMPLETING???=?1;
????private?static?final?int?NORMAL???????=?2;
????private?static?final?int?EXCEPTIONAL??=?3;
????private?static?final?int?CANCELLED????=?4;
????private?static?final?int?INTERRUPTING?=?5;
????private?static?final?int?INTERRUPTED??=?6;

阻塞的真相水落石出啦,FutureTask的狀態大于COMPLETING才會返回,要不然都會一直阻塞等待 。又因為拒絕策略啥沒做,沒有修改FutureTask的狀態,因此FutureTask的狀態一直是NEW,所以它不會返回,會一直等待。

這個問題,可以使用別的拒絕策略,比如CallerRunsPolicy,它讓主線程去執行拒絕的任務,會更新FutureTask狀態。如果確實想用DiscardPolicy,則需要重寫DiscardPolicy的拒絕策略。

溫馨提示 ,日常開發中,使用 Future.get() 時,盡量使用帶超時時間的 ,因為它是阻塞的。

future.get(1,TimeUnit.SECONDS);

難道使用別的拒絕策略,就萬無一失了嘛? 不是的,如果使用CallerRunsPolicy拒絕策略,它表示拒絕的任務給調用方線程用,如果這是主線程,那會不會可能也導致主線程阻塞 呢?總結起來,大家日常開發的時候,多一份心眼吧,多一點思考吧。

5. Spring內部線程池的坑

工作中,個別開發者,為了快速開發,喜歡直接用spring的@Async,來執行異步任務。

@Async
publicvoidtestAsync()throwsInterruptedException{
System.out.println("處理異步任務");
TimeUnit.SECONDS.sleep(newRandom().nextInt(100));
}

Spring內部線程池,其實是SimpleAsyncTaskExecutor,這玩意有點坑,它不會復用線程的 ,它的設計初衷就是執行大量的短時間的任務。有興趣的小伙伴,可以去看看它的源碼:

/**
*{@linkTaskExecutor}implementationthatfiresupanewThreadforeachtask,
*executingitasynchronously.
*
*

Supportslimitingconcurrentthreadsthroughthe"concurrencyLimit" *beanproperty.Bydefault,thenumberofconcurrentthreadsisunlimited. * *

NOTE:Thisimplementationdoesnotreusethreads!Considera *thread-poolingTaskExecutorimplementationinstead,inparticularfor *executingalargenumberofshort-livedtasks. * *@authorJuergenHoeller *@since2.0 *@see#setConcurrencyLimit *@seeSyncTaskExecutor *@seeorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor *@seeorg.springframework.scheduling.commonj.WorkManagerTaskExecutor */ @SuppressWarnings("serial") publicclassSimpleAsyncTaskExecutorextendsCustomizableThreadCreatorimplementsAsyncListenableTaskExecutor,Serializable{ ...... }

也就是說來了一個請求,就會新建一個線程!大家使用spring的@Async時,要避開這個坑,自己再定義一個線程池。正例如下:

@Bean(name="threadPoolTaskExecutor")
publicExecutorthreadPoolTaskExecutor(){
ThreadPoolTaskExecutorexecutor=newThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setThreadNamePrefix("tianluo-%d");
//其他參數設置
returnnewThreadPoolTaskExecutor();
}

6. 使用線程池時,沒有自定義命名

使用線程池時,如果沒有給線程池一個有意義的名稱,將不好排查回溯問題。這不算一個坑吧,只能說給以后排查埋坑 ,哈哈。我還是單獨把它放出來算一個點,因為個人覺得這個還是比較重要的。反例如下:

publicclassThreadTest{

publicstaticvoidmain(String[]args)throwsException{
ThreadPoolExecutorexecutorOne=newThreadPoolExecutor(5,5,1,
TimeUnit.MINUTES,newArrayBlockingQueue(20));
executorOne.execute(()->{
System.out.println("關注:芋道源碼");
thrownewNullPointerException();
});
}
}

運行結果:

Exceptioninthread"pool-1-thread-1"java.lang.NullPointerException
atcom.example.dto.ThreadTest.lambda$main$0(ThreadTest.java:17)
atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
atjava.lang.Thread.run(Thread.java:748)

可以發現,默認打印的線程池名字是pool-1-thread-1,如果排查問題起來,并不友好。因此建議大家給自己線程池自定義個容易識別的名字。其實用CustomizableThreadFactory即可,正例如下:

publicclassThreadTest{

publicstaticvoidmain(String[]args)throwsException{
ThreadPoolExecutorexecutorOne=newThreadPoolExecutor(5,5,1,
TimeUnit.MINUTES,newArrayBlockingQueue(20),newCustomizableThreadFactory("Tianluo-Thread-pool"));
executorOne.execute(()->{
System.out.println("關注:芋道源碼");
thrownewNullPointerException();
});
}
}

7. 線程池參數設置不合理

線程池最容易出坑的地方,就是線程參數設置不合理。比如核心線程設置多少合理,最大線程池設置多少合理等等。當然,這塊不是亂設置的,需要結合具體業務

比如線程池如何調優,如何確認最佳線程數?

最佳線程數目=((線程等待時間+線程CPU時間)/線程CPU時間)*CPU數目

我們的服務器CPU核數為8核,一個任務線程cpu耗時為20ms,線程等待(網絡IO、磁盤IO)耗時80ms,那最佳線程數目:( 80 + 20 )/20 * 8 = 40。也就是設置 40個線程數最佳。

8. 線程池異常處理的坑

我們來看段代碼:

publicclassThreadTest{

publicstaticvoidmain(String[]args)throwsException{
ThreadPoolExecutorexecutorOne=newThreadPoolExecutor(5,5,1,
TimeUnit.MINUTES,newArrayBlockingQueue(20),newCustomizableThreadFactory("Tianluo-Thread-pool"));
for(inti=0;i{
System.out.println("currentthreadname"+Thread.currentThread().getName());
Objectobject=null;
System.out.print("result#"+object.toString());
});
}

}
}

按道理,運行這塊代碼應該拋空指針異常 才是的,對吧。但是,運行結果卻是這樣的;

currentthreadnameTianluo-Thread-pool1
currentthreadnameTianluo-Thread-pool2
currentthreadnameTianluo-Thread-pool3
currentthreadnameTianluo-Thread-pool4
currentthreadnameTianluo-Thread-pool5

這是因為使用submit提交任務,不會把異常直接這樣拋出來。大家有興趣的話,可以去看看源碼。可以改為execute方法執行,當然最好就是try...catch捕獲,如下:

publicclassThreadTest{

publicstaticvoidmain(String[]args)throwsException{
ThreadPoolExecutorexecutorOne=newThreadPoolExecutor(5,5,1,
TimeUnit.MINUTES,newArrayBlockingQueue(20),newCustomizableThreadFactory("Tianluo-Thread-pool"));
for(inti=0;i{
System.out.println("currentthreadname"+Thread.currentThread().getName());
try{
Objectobject=null;
System.out.print("result#"+object.toString());
}catch(Exceptione){
System.out.println("異常了"+e);
}
});
}

}
}

其實,我們還可以為工作者線程設置UncaughtExceptionHandler,在uncaughtException方法中處理異常。大家知道這個坑就好啦。

9. 線程池使用完畢后,忘記關閉

如果線程池使用完,忘記關閉的話,有可能會導致內存泄露 問題。所以,大家使用完線程池后,記得關閉一下。同時,線程池最好也設計成單例模式,給它一個好的命名,以方便排查問題。

publicclassThreadTest{

publicstaticvoidmain(String[]args)throwsException{

ThreadPoolExecutorexecutorOne=newThreadPoolExecutor(5,5,1,
TimeUnit.MINUTES,newArrayBlockingQueue(20),newCustomizableThreadFactory("Tianluo-Thread-pool"));
executorOne.execute(()->{
System.out.println("關注:芋道源碼");
});

//關閉線程池
executorOne.shutdown();
}
}

10. ThreadLocal與線程池搭配,線程復用,導致信息錯亂。

使用ThreadLocal緩存信息,如果配合線程池一起,有可能出現信息錯亂的情況。先看下一下例子:

privatestaticfinalThreadLocalcurrentUser=ThreadLocal.withInitial(()->null);

@GetMapping("wrong")
publicMapwrong(@RequestParam("userId")IntegeruserId){
//設置用戶信息之前先查詢一次ThreadLocal中的用戶信息
Stringbefore=Thread.currentThread().getName()+":"+currentUser.get();
//設置用戶信息到ThreadLocal
currentUser.set(userId);
//設置用戶信息之后再查詢一次ThreadLocal中的用戶信息
Stringafter=Thread.currentThread().getName()+":"+currentUser.get();
//匯總輸出兩次查詢結果
Mapresult=newHashMap();
result.put("before",before);
result.put("after",after);
returnresult;
}

按理說,每次獲取的before應該都是null,但是呢,程序運行在 Tomcat 中,執行程序的線程是Tomcat的工作線程,而Tomcat的工作線程是基于線程池 的。

線程池會重用固定的幾個線程 ,一旦線程重用,那么很可能首次從 ThreadLocal 獲取的值是之前其他用戶的請求遺留的值。這時,ThreadLocal 中的用戶信息就是其他用戶的信息。

把tomcat的工作線程設置為1

server.tomcat.max-threads=1

用戶1,請求過來,會有以下結果,符合預期:

29627f00-0bea-11ee-962d-dac502259ad0.png

用戶2請求過來,會有以下結果,「不符合預期」:

298af502-0bea-11ee-962d-dac502259ad0.png

因此,使用類似 ThreadLocal 工具來存放一些數據時,需要特別注意在代碼運行完后,顯式地去清空設置的數據,正例如下:

@GetMapping("right")
publicMapright(@RequestParam("userId")IntegeruserId){
Stringbefore=Thread.currentThread().getName()+":"+currentUser.get();
currentUser.set(userId);
try{
Stringafter=Thread.currentThread().getName()+":"+currentUser.get();
Mapresult=newHashMap();
result.put("before",before);
result.put("after",after);
returnresult;
}finally{
//在finally代碼塊中刪除ThreadLocal中的數據,確保數據不串
currentUser.remove();
}
}




審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 線程池
    +關注

    關注

    0

    文章

    57

    瀏覽量

    7030
  • JDK
    JDK
    +關注

    關注

    0

    文章

    82

    瀏覽量

    16752
  • Thread
    +關注

    關注

    2

    文章

    83

    瀏覽量

    26243

原文標題:細數線程池的10個坑,面試線程不怕不怕啦

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    Java中的線程包括哪些

    java.util.concurrent 包來實現的,最主要的就是 ThreadPoolExecutor 類。 Executor: 代表線程的接口,有一 execute() 方法,給一
    的頭像 發表于 10-11 15:33 ?933次閱讀
    Java中的<b class='flag-5'>線程</b><b class='flag-5'>池</b>包括哪些

    線程是如何實現的

    線程的概念是什么?線程是如何實現的?
    發表于 02-28 06:20

    java自帶的線程方法

    二、原理分析 從上面使用線程的例子來看,最主要就是兩步,構造ThreadPoolExecutor對象,然后每來一任務,就調用ThreadPoolExecutor對象的execute方法。 1
    發表于 09-27 11:06 ?0次下載

    如何正確關閉線程

    前言本章分為兩議題 如何正確關閉線程 shutdown 和 shutdownNow 的區別 項目環境jdk 1.8 github 地址:https://github.com
    的頭像 發表于 09-29 14:41 ?1w次閱讀

    基于Nacos的簡單動態化線程實現

    本文以Nacos作為服務配置中心,以修改線程核心線程數、最大線程數為例,實現一簡單的動態化線程
    發表于 01-06 14:14 ?967次閱讀

    線程線程

    線程通常用于服務器應用程序。 每個傳入請求都將分配給線程池中的一線程,因此可以異步處理請求,而不會占用主
    的頭像 發表于 02-28 09:53 ?937次閱讀
    多<b class='flag-5'>線程</b>之<b class='flag-5'>線程</b><b class='flag-5'>池</b>

    Java線程核心原理

    看過Java線程源碼的小伙伴都知道,在Java線程池中最核心的類就是ThreadPoolExecutor,
    的頭像 發表于 04-21 10:24 ?992次閱讀

    線程線程怎么釋放

    線程分組看,pool名開頭線程占616條,而且waiting狀態也是616條,這個點就非常可疑了,我斷定就是這個pool開頭線程導致的問題。我們先排查為何這個
    發表于 07-31 10:49 ?2451次閱讀
    <b class='flag-5'>線程</b><b class='flag-5'>池</b>的<b class='flag-5'>線程</b>怎么釋放

    線程的兩思考

    今天還是說一下線程的兩思考。 池子 我們常用的線程, JDK的ThreadPoolExecutor. CompletableFutur
    的頭像 發表于 09-30 11:21 ?3216次閱讀
    <b class='flag-5'>線程</b><b class='flag-5'>池</b>的兩<b class='flag-5'>個</b>思考

    Spring 的線程應用

    我們在日常開發中,經常跟多線程打交道,Spring 為我們提供了一線程方便我們開發,它就是 ThreadPoolTaskExecutor ,接下來我們就來聊聊 Spring 的
    的頭像 發表于 10-13 10:47 ?710次閱讀
    Spring 的<b class='flag-5'>線程</b><b class='flag-5'>池</b>應用

    線程基本概念與原理

    、17、20等的新特性,簡化了多線程編程的實現。 提高性能與資源利用率 線程主要解決兩問題:線程創建與銷毀的開銷以及
    的頭像 發表于 11-10 10:24 ?752次閱讀

    線程的基本概念

    線程的基本概念 不管線程是什么東西!但是我們必須知道線程被搞出來的目的就是:提高程序執行效
    的頭像 發表于 11-10 16:37 ?653次閱讀
    <b class='flag-5'>線程</b><b class='flag-5'>池</b>的基本概念

    線程七大核心參數執行順序

    以及它們的執行順序。 corePoolSize(核心線程數): 線程池中一直存活的線程數量。在線程初始化或者任務提交后,
    的頭像 發表于 12-04 16:45 ?1303次閱讀

    線程的創建方式有幾種

    線程是一種用于管理和調度線程的技術,能夠有效地提高系統的性能和資源利用率。它通過預先創建一組線程并維護一工作隊列,將任務提交給
    的頭像 發表于 12-04 16:52 ?1076次閱讀

    什么是動態線程?動態線程的簡單實現思路

    因此,動態可監控線程一種針對以上痛點開發的線程管理工具。主要可實現功能有:提供對 Spring 應用內線程
    的頭像 發表于 02-28 10:42 ?903次閱讀
    主站蜘蛛池模板: 日本大片在线看 | 色香欲亚洲天天综合网 | 2019天天操天天干天天透 | 美日韩一区二区 | 18美女扒开尿口无遮挡 | 欧美激情第一欧美在线 | 性高清| sihu在线| 亚洲卡1卡2卡新区网站 | 天天干干| 正在播放国产乱子伦视频 | 明日花绮罗snis-862在线播放 | 色网站在线看 | 7777色鬼xxxx欧美色夫 | 四虎国产视频 | 欧美色炮 | 婷婷久久综合九色综合九七 | 日本黄色片段 | 在线观看日本免费视频大片一区 | 在线色资源 | 亚洲播放 | 操日本美女视频 | 色视频在线网站 | 中文网丁香综合网 | yy6080理aa级伦大片一级 | 手机在线小视频 | 成人三级在线播放线观看 | 男人和女人做爽爽视频在线观看 | 亚洲视频一区二区三区 | 中文字幕一二三四区2021 | 成年人黄色大片大全 | 国产色婷婷精品综合在线观看 | 免费一区二区视频 | 三级完整在线观看高清视频 | 狠狠的日视频 | 18性夜影院午夜寂寞影院免费 | 51精品视频免费国产专区 | 91夜夜人人揉人人捏人人添 | 欧美 变态 另类 人妖班 | 免费午夜视频 | 国产产一区二区三区久久毛片国语 |